use alloc::borrow::Cow;
use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen};
use scale_info::TypeInfo;
use subsoil::runtime::{
traits::{ConstU32, Hash},
DispatchError,
};
use Debug;
pub type BoundedInline = crate::BoundedVec<u8, ConstU32<128>>;
const MAX_LEGACY_LEN: u32 = 1_000_000;
#[derive(
Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, Clone, Eq, PartialEq, TypeInfo, Debug,
)]
#[codec(mel_bound())]
pub enum Bounded<T, H: Hash> {
Legacy { hash: H::Output, dummy: core::marker::PhantomData<T> },
Inline(BoundedInline),
Lookup { hash: H::Output, len: u32 },
}
impl<T, H: Hash> Bounded<T, H> {
pub fn transmute<S: Encode>(self) -> Bounded<S, H>
where
T: Encode + EncodeLike<S>,
{
use Bounded::*;
match self {
Legacy { hash, .. } => Legacy { hash, dummy: core::marker::PhantomData },
Inline(x) => Inline(x),
Lookup { hash, len } => Lookup { hash, len },
}
}
pub fn hash(&self) -> H::Output {
use Bounded::*;
match self {
Lookup { hash, .. } | Legacy { hash, .. } => *hash,
Inline(x) => <H as Hash>::hash(x.as_ref()),
}
}
pub fn lookup_hash(&self) -> Option<H::Output> {
use Bounded::*;
match self {
Lookup { hash, .. } | Legacy { hash, .. } => Some(*hash),
Inline(_) => None,
}
}
pub fn len(&self) -> Option<u32> {
match self {
Self::Legacy { .. } => None,
Self::Inline(i) => Some(i.len() as u32),
Self::Lookup { len, .. } => Some(*len),
}
}
pub fn lookup_needed(&self) -> bool {
match self {
Self::Inline(..) => false,
Self::Legacy { .. } | Self::Lookup { .. } => true,
}
}
pub fn lookup_len(&self) -> Option<u32> {
match self {
Self::Inline(..) => None,
Self::Legacy { .. } => Some(MAX_LEGACY_LEN),
Self::Lookup { len, .. } => Some(*len),
}
}
pub fn unrequested(hash: H::Output, len: u32) -> Self {
Self::Lookup { hash, len }
}
#[deprecated = "This API is only for transitioning to Scheduler v3 API"]
pub fn from_legacy_hash(hash: impl Into<H::Output>) -> Self {
Self::Legacy { hash: hash.into(), dummy: core::marker::PhantomData }
}
}
pub type FetchResult = Result<Cow<'static, [u8]>, DispatchError>;
pub trait QueryPreimage {
type H: Hash;
fn len(hash: &<Self::H as subsoil::core::Hasher>::Out) -> Option<u32>;
fn fetch(hash: &<Self::H as subsoil::core::Hasher>::Out, len: Option<u32>) -> FetchResult;
fn is_requested(hash: &<Self::H as subsoil::core::Hasher>::Out) -> bool;
fn request(hash: &<Self::H as subsoil::core::Hasher>::Out);
fn unrequest(hash: &<Self::H as subsoil::core::Hasher>::Out);
fn hold<T>(bounded: &Bounded<T, Self::H>) {
use Bounded::*;
match bounded {
Inline(..) => {},
Legacy { hash, .. } | Lookup { hash, .. } => Self::request(hash),
}
}
fn drop<T>(bounded: &Bounded<T, Self::H>) {
use Bounded::*;
match bounded {
Inline(..) => {},
Legacy { hash, .. } | Lookup { hash, .. } => Self::unrequest(hash),
}
}
fn have<T>(bounded: &Bounded<T, Self::H>) -> bool {
use Bounded::*;
match bounded {
Inline(..) => true,
Legacy { hash, .. } | Lookup { hash, .. } => Self::len(hash).is_some(),
}
}
fn pick<T>(hash: <Self::H as subsoil::core::Hasher>::Out, len: u32) -> Bounded<T, Self::H> {
Self::request(&hash);
Bounded::Lookup { hash, len }
}
fn peek<T: Decode>(bounded: &Bounded<T, Self::H>) -> Result<(T, Option<u32>), DispatchError> {
use Bounded::*;
match bounded {
Inline(data) => T::decode(&mut &data[..]).ok().map(|x| (x, None)),
Lookup { hash, len } => {
let data = Self::fetch(hash, Some(*len))?;
T::decode(&mut &data[..]).ok().map(|x| (x, Some(data.len() as u32)))
},
Legacy { hash, .. } => {
let data = Self::fetch(hash, None)?;
T::decode(&mut &data[..]).ok().map(|x| (x, Some(data.len() as u32)))
},
}
.ok_or(DispatchError::Corruption)
}
fn realize<T: Decode>(
bounded: &Bounded<T, Self::H>,
) -> Result<(T, Option<u32>), DispatchError> {
let r = Self::peek(bounded)?;
Self::drop(bounded);
Ok(r)
}
}
pub trait StorePreimage: QueryPreimage {
const MAX_LENGTH: usize;
fn note(bytes: Cow<[u8]>) -> Result<<Self::H as subsoil::core::Hasher>::Out, DispatchError>;
fn unnote(hash: &<Self::H as subsoil::core::Hasher>::Out) {
Self::unrequest(hash)
}
fn bound<T: Encode>(t: T) -> Result<Bounded<T, Self::H>, DispatchError> {
let data = t.encode();
let len = data.len() as u32;
Ok(match BoundedInline::try_from(data) {
Ok(bounded) => Bounded::Inline(bounded),
Err(unbounded) => Bounded::Lookup { hash: Self::note(unbounded.into())?, len },
})
}
}
impl QueryPreimage for () {
type H = subsoil::runtime::traits::BlakeTwo256;
fn len(_: &subsoil::core::H256) -> Option<u32> {
None
}
fn fetch(_: &subsoil::core::H256, _: Option<u32>) -> FetchResult {
Err(DispatchError::Unavailable)
}
fn is_requested(_: &subsoil::core::H256) -> bool {
false
}
fn request(_: &subsoil::core::H256) {}
fn unrequest(_: &subsoil::core::H256) {}
}
impl StorePreimage for () {
const MAX_LENGTH: usize = 0;
fn note(_: Cow<[u8]>) -> Result<subsoil::core::H256, DispatchError> {
Err(DispatchError::Exhausted)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BoundedVec;
use subsoil::runtime::{bounded_vec, traits::BlakeTwo256};
#[test]
fn bounded_size_is_correct() {
assert_eq!(<Bounded<Vec<u8>, BlakeTwo256> as MaxEncodedLen>::max_encoded_len(), 131);
}
#[test]
fn bounded_basic_works() {
let data: BoundedVec<u8, _> = bounded_vec![b'a', b'b', b'c'];
let len = data.len() as u32;
let hash = BlakeTwo256::hash(&data).into();
{
let bound: Bounded<Vec<u8>, BlakeTwo256> = Bounded::Inline(data.clone());
assert_eq!(bound.hash(), hash);
assert_eq!(bound.len(), Some(len));
assert!(!bound.lookup_needed());
assert_eq!(bound.lookup_len(), None);
}
{
let bound: Bounded<Vec<u8>, BlakeTwo256> =
Bounded::Legacy { hash, dummy: Default::default() };
assert_eq!(bound.hash(), hash);
assert_eq!(bound.len(), None);
assert!(bound.lookup_needed());
assert_eq!(bound.lookup_len(), Some(1_000_000));
}
{
let bound: Bounded<Vec<u8>, BlakeTwo256> =
Bounded::Lookup { hash, len: data.len() as u32 };
assert_eq!(bound.hash(), hash);
assert_eq!(bound.len(), Some(len));
assert!(bound.lookup_needed());
assert_eq!(bound.lookup_len(), Some(len));
}
}
#[test]
fn bounded_transmuting_works() {
let data: BoundedVec<u8, _> = bounded_vec![b'a', b'b', b'c'];
let x: Bounded<String, BlakeTwo256> = Bounded::Inline(data.clone());
let y: Bounded<&str, BlakeTwo256> = x.transmute();
assert_eq!(y, Bounded::Inline(data));
}
}