use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use sp_core::{RuntimeDebug, H256};
use sp_io::hashing::blake2_256;
use sp_runtime::{traits::ConstU32, DispatchError};
use sp_std::borrow::Cow;
pub type Hash = H256;
pub type BoundedInline = crate::BoundedVec<u8, ConstU32<128>>;
const MAX_LEGACY_LEN: u32 = 1_000_000;
#[derive(
Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug,
)]
#[codec(mel_bound())]
pub enum Bounded<T> {
Legacy { hash: Hash, dummy: sp_std::marker::PhantomData<T> },
Inline(BoundedInline),
Lookup { hash: Hash, len: u32 },
}
impl<T> Bounded<T> {
pub fn transmute<S: Encode>(self) -> Bounded<S>
where
T: Encode + EncodeLike<S>,
{
use Bounded::*;
match self {
Legacy { hash, .. } => Legacy { hash, dummy: sp_std::marker::PhantomData },
Inline(x) => Inline(x),
Lookup { hash, len } => Lookup { hash, len },
}
}
pub fn hash(&self) -> Hash {
use Bounded::*;
match self {
Lookup { hash, .. } | Legacy { hash, .. } => *hash,
Inline(x) => blake2_256(x.as_ref()).into(),
}
}
pub fn lookup_hash(&self) -> Option<Hash> {
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: Hash, 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<Hash>) -> Self {
Self::Legacy { hash: hash.into(), dummy: sp_std::marker::PhantomData }
}
}
pub type FetchResult = Result<Cow<'static, [u8]>, DispatchError>;
pub trait QueryPreimage {
fn len(hash: &Hash) -> Option<u32>;
fn fetch(hash: &Hash, len: Option<u32>) -> FetchResult;
fn is_requested(hash: &Hash) -> bool;
fn request(hash: &Hash);
fn unrequest(hash: &Hash);
fn hold<T>(bounded: &Bounded<T>) {
use Bounded::*;
match bounded {
Inline(..) => {},
Legacy { hash, .. } | Lookup { hash, .. } => Self::request(hash),
}
}
fn drop<T>(bounded: &Bounded<T>) {
use Bounded::*;
match bounded {
Inline(..) => {},
Legacy { hash, .. } | Lookup { hash, .. } => Self::unrequest(hash),
}
}
fn have<T>(bounded: &Bounded<T>) -> bool {
use Bounded::*;
match bounded {
Inline(..) => true,
Legacy { hash, .. } | Lookup { hash, .. } => Self::len(hash).is_some(),
}
}
fn pick<T>(hash: Hash, len: u32) -> Bounded<T> {
Self::request(&hash);
Bounded::Lookup { hash, len }
}
fn peek<T: Decode>(bounded: &Bounded<T>) -> 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>) -> 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<Hash, DispatchError>;
fn unnote(hash: &Hash) {
Self::unrequest(hash)
}
fn bound<T: Encode>(t: T) -> Result<Bounded<T>, 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 () {
fn len(_: &Hash) -> Option<u32> {
None
}
fn fetch(_: &Hash, _: Option<u32>) -> FetchResult {
Err(DispatchError::Unavailable)
}
fn is_requested(_: &Hash) -> bool {
false
}
fn request(_: &Hash) {}
fn unrequest(_: &Hash) {}
}
impl StorePreimage for () {
const MAX_LENGTH: usize = 0;
fn note(_: Cow<[u8]>) -> Result<Hash, DispatchError> {
Err(DispatchError::Exhausted)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{bounded_vec, BoundedVec};
#[test]
fn bounded_size_is_correct() {
assert_eq!(<Bounded<Vec<u8>> 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 = blake2_256(&data).into();
{
let bound: Bounded<Vec<u8>> = 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>> = 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>> = 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> = Bounded::Inline(data.clone());
let y: Bounded<&str> = x.transmute();
assert_eq!(y, Bounded::Inline(data));
}
}