Repository: Phala-Network/phala-blockchain (language: rust)

Issue:
Some update for subrpc
- [x] Expose ss58, so that we can derive addresses in other modules
- [x] Delete unused error definitions, case many of them will never be used in subrpc
- [x] Introduce `ExtraParams` (adding `nonce`) when creating transactions, this would be very helpful when we want to construct a transaction with a given `nonce`


Discussion / hints:
I force use mortal cause, it's hard to make it optional default, because compute era will send RPC which could return error, which can not hander in `default()`

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 326b9cf3be..2584a4db66 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8146,6 +8146,7 @@ dependencies = [
  "base58",
  "hex",
  "hex-literal",
+ "impl-serde 0.4.0",
  "parity-scale-codec",
  "pink-extension 0.2.0",
  "pink-extension-runtime 0.2.0",
diff --git a/crates/pink-libs/subrpc/Cargo.toml b/crates/pink-libs/subrpc/Cargo.toml
index 15be648e44..5cb4642d4c 100644
--- a/crates/pink-libs/subrpc/Cargo.toml
+++ b/crates/pink-libs/subrpc/Cargo.toml
@@ -19,6 +19,7 @@ primitive-types = { version = "0.11", default-features = false, features = ["cod
 
 hex-literal = "0.3"
 hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
+impl-serde = { version = "0.4.0", optional = true }
 
 pink-extension = { version = "0.2", path = "../../pink/pink-extension", default-features = false, features = ["ink-as-dependency"] }
 sp-core-hashing = { version = "6.0.0", default-features = false }
@@ -38,6 +39,7 @@ default = ["std"]
 std = [
     "scale/std",
     "scale-info/std",
+    "impl-serde",
     "pink-extension/std",
     "pink-json/std",
     "sp-core-hashing/std",
diff --git a/crates/pink-libs/subrpc/src/lib.rs b/crates/pink-libs/subrpc/src/lib.rs
index 83a27fd9a2..c4474495a3 100644
--- a/crates/pink-libs/subrpc/src/lib.rs
+++ b/crates/pink-libs/subrpc/src/lib.rs
@@ -9,19 +9,21 @@ use alloc::format;
 use alloc::string::{String, ToString};
 use alloc::vec::Vec;
 use pink_extension::chain_extension::{signing, SigType};
-use scale::{Compact, Encode};
 
 use pink_json as json;
+use scale::{Compact, Decode, Encode};
 
 mod objects;
+mod primitives;
 mod rpc;
 mod ss58;
 pub mod storage;
 mod transaction;
 
 use objects::*;
+pub use primitives::era::Era;
 use rpc::call_rpc;
-use ss58::{get_ss58addr_version, Ss58Codec};
+pub use ss58::{get_ss58addr_version, Ss58Codec};
 use transaction::{MultiAddress, MultiSignature, Signature, UnsignedExtrinsic};
 
 pub mod traits {
@@ -31,23 +33,11 @@ pub mod traits {
         #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)]
         #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
         pub enum Error {
-            BadAbi,
-            BadOrigin,
-            AssetAlreadyRegistered,
-            AssetNotFound,
-            ChainAlreadyRegistered,
-            ChainNotFound,
-            ExtractLocationFailed,
-            InvalidAddress,
-            ConstructContractFailed,
-            FetchDataFailed,
-            Unimplemented,
-            InvalidMultilocation,
-            InvalidAmount,
             SubRPCRequestFailed,
             InvalidBody,
             InvalidSignature,
             Ss58,
+            ParseFailed,
         }
     }
 }
@@ -76,7 +66,7 @@ pub fn get_storage(rpc_node: &str, key: &[u8], at: Option<H256>) -> Result<Optio
 ///
 /// Nonce represents how many transactions the account has successfully issued
 /// TODO: simplify
-pub fn get_next_nonce(rpc_node: &str, ss58_addr: &str) -> core::result::Result<NextNonceOk, Error> {
+pub fn get_next_nonce(rpc_node: &str, ss58_addr: &str) -> core::result::Result<u64, Error> {
     // TODO: can we contruct the json object using serde_json_core?
     let data = format!(
         r#"{{"id":1,"jsonrpc":"2.0","method":"system_accountNextIndex","params":["{}"]}}"#,
@@ -91,7 +81,7 @@ pub fn get_next_nonce(rpc_node: &str, ss58_addr: &str) -> core::result::Result<N
         next_nonce: next_nonce.result,
     };
 
-    Ok(next_nonce_ok)
+    Ok(next_nonce_ok.next_nonce)
 }
 
 // TODO: simplify
@@ -100,28 +90,32 @@ pub fn get_runtime_version(rpc_node: &str) -> core::result::Result<RuntimeVersio
         .to_string()
         .into_bytes();
     let resp_body = call_rpc(rpc_node, data)?;
-
     let runtime_version: RuntimeVersion =
         json::from_slice(&resp_body).or(Err(Error::InvalidBody))?;
 
     let runtime_version_result = runtime_version.result;
     let mut api_vec: Vec<(String, u32)> = Vec::new();
     for (api_str, api_u32) in runtime_version_result.apis {
-        api_vec.push((api_str.to_string().parse().unwrap(), api_u32));
+        api_vec.push((
+            api_str
+                .to_string()
+                .parse()
+                .map_err(|_| Error::ParseFailed)?,
+            api_u32,
+        ));
     }
 
     let runtime_version_ok = RuntimeVersionOk {
-        // TODO: replace the upwraps
         spec_name: runtime_version_result
             .spec_name
             .to_string()
             .parse()
-            .unwrap(),
+            .map_err(|_| Error::ParseFailed)?,
         impl_name: runtime_version_result
             .impl_name
             .to_string()
             .parse()
-            .unwrap(),
+            .map_err(|_| Error::ParseFailed)?,
         authoring_version: runtime_version_result.authoring_version,
         spec_version: runtime_version_result.spec_version,
         impl_version: runtime_version_result.impl_version,
@@ -156,6 +150,59 @@ pub fn get_block_hash(
     Ok(H256(hash))
 }
 
+/// Gets revalant block header by given block hash
+/// Only work with chains define generic patterns like `Header<u32, BlakeTwo256>`
+pub fn get_header(
+    rpc_node: &str,
+    block_hash: Option<H256>,
+) -> core::result::Result<BlockHeaderOk, Error> {
+    let param = block_hash.map_or("null".to_string(), |h| format!("\"0x{h:x}\""));
+
+    let data =
+        format!(r#"{{"id":1, "jsonrpc":"2.0", "method": "chain_getHeader","params":[{param}]}}"#)
+            .into_bytes();
+    let resp_body = call_rpc(rpc_node, data)?;
+    let header: BlockHeader = json::from_slice(&resp_body)
+        .or(Err(Error::InvalidBody))
+        .unwrap();
+
+    let header_result = header.result;
+    let decoded_parent_hash =
+        hex::decode(&header_result.parent_hash[2..]).or(Err(Error::InvalidBody))?;
+    let decoded_state_root =
+        hex::decode(&header_result.state_root[2..]).or(Err(Error::InvalidBody))?;
+    let decoded_extrinsics_root =
+        hex::decode(&header_result.extrinsics_root[2..]).or(Err(Error::InvalidBody))?;
+    Ok(BlockHeaderOk {
+        parent_hash: decoded_parent_hash.try_into().or(Err(Error::InvalidBody))?,
+        number: u32::from_str_radix(&header_result.number[2..], 16).expect("block number overflow"),
+        state_root: decoded_state_root.try_into().or(Err(Error::InvalidBody))?,
+        extrinsics_root: decoded_extrinsics_root
+            .try_into()
+            .or(Err(Error::InvalidBody))?,
+        digest: header_result.digest,
+    })
+}
+
+#[derive(Default, Encode, Decode, Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub struct ExtraParam {
+    // Tip for the block producer.
+    pub tip: u128,
+    // Account nonce along with extrinsic
+    pub nonce: Option<u64>,
+    // Longevity of a transaction
+    pub era: Option<Era>,
+}
+
+fn compute_era(block_number: u64) -> core::result::Result<Era, Error> {
+    // The transaction longevity, should be a power of two between 4 and 65536. unit: bloc
+    let longevity = 4;
+    let period = longevity;
+    let phase = block_number % period;
+    Ok(Era::Mortal(period, phase))
+}
+
 /// Creates an extrinsic
 ///
 /// An extended version of `create_transaction`, fine-grain
@@ -166,6 +213,7 @@ pub fn create_transaction_ext<T: Encode>(
     nonce: u64,
     spec_version: u32,
     transaction_version: u32,
+    era_checkpoint: &[u8; 32],
     genesis_hash: &[u8; 32],
     call_data: UnsignedExtrinsic<T>,
     era: Era,
@@ -175,7 +223,7 @@ pub fn create_transaction_ext<T: Encode>(
         spec_version,
         transaction_version,
         genesis_hash,
-        genesis_hash,
+        era_checkpoint,
     );
     let extra = (era, Compact(nonce), Compact(tip));
 
@@ -233,19 +281,32 @@ pub fn create_transaction<T: Encode>(
     pallet_id: u8,
     call_id: u8,
     data: T,
+    extra: ExtraParam,
 ) -> core::result::Result<Vec<u8>, Error> {
     let version = get_ss58addr_version(chain)?;
     let public_key: [u8; 32] = signing::get_public_key(signer, SigType::Sr25519)
         .try_into()
-        .unwrap();
+        .expect("Public key conversion failed");
     let addr = public_key.to_ss58check_with_version(version.prefix());
-    let nonce = get_next_nonce(rpc_node, &addr)?.next_nonce;
     let runtime_version = get_runtime_version(rpc_node)?;
     let genesis_hash: [u8; 32] = get_genesis_hash(rpc_node)?.0;
+
     let spec_version = runtime_version.spec_version;
     let transaction_version = runtime_version.transaction_version;
-    let era = Era::Immortal;
-    let tip: u128 = 0;
+    let (era_checkpoint, era) = match extra.era {
+        Some(Era::Immortal) => (genesis_hash, Era::Immortal),
+        _ => {
+            let header = get_header(rpc_node, <Option<H256>>::None)?;
+            let era = extra.era.unwrap_or(compute_era(header.number as u64)?);
+            (header.hash(), era)
+        }
+    };
+    let tip = extra.tip;
+    let nonce = match extra.nonce {
+        Some(n) => n,
+        _ => get_next_nonce(rpc_node, &addr)?,
+    };
+
     let call_data = UnsignedExtrinsic {
         pallet_id,
         call_id,
@@ -257,6 +318,7 @@ pub fn create_transaction<T: Encode>(
         nonce,
         spec_version,
         transaction_version,
+        &era_checkpoint,
         &genesis_hash,
         call_data,
         era,
@@ -273,6 +335,7 @@ pub fn send_transaction(rpc_node: &str, signed_tx: &[u8]) -> core::result::Resul
     .into_bytes();
     let resp_body = call_rpc(rpc_node, data)?;
     let resp: TransactionResponse = json::from_slice(&resp_body).or(Err(Error::InvalidBody))?;
+
     hex::decode(&resp.result[2..]).or(Err(Error::InvalidBody))
 }
 
@@ -375,9 +438,17 @@ mod tests {
         let signer: [u8; 32] =
             hex!("9eb2ee60393aeeec31709e256d448c9e40fa64233abf12318f63726e9c417b69");
         let remark = "Greetings from unit tests!".to_string();
-        let signed_tx = create_transaction(&signer, "khala", rpc_node, 0u8, 1u8, remark);
+        let signed_tx = create_transaction(
+            &signer,
+            "khala",
+            rpc_node,
+            0u8,
+            1u8,
+            remark,
+            ExtraParam::default(),
+        );
         if signed_tx.is_err() {
-            println!("failed to signed tx");
+            println!("failed to sign tx");
             return;
         };
         let signed_tx = signed_tx.unwrap();
@@ -387,7 +458,7 @@ mod tests {
             return;
         }
         let tx_id = tx_id.unwrap();
-        // https://khala.subscan.io/extrinsic/2676952-2
+        // https://khala.subscan.io/extrinsic/3083306-2
         dbg!(hex::encode(tx_id));
     }
 
@@ -439,9 +510,10 @@ mod tests {
             0x52u8,
             0x0u8,
             (multi_asset, dest, dest_weight),
+            ExtraParam::default(),
         );
         if signed_tx.is_err() {
-            println!("failed to signed tx");
+            println!("failed to sign tx");
             return;
         };
         let signed_tx = signed_tx.unwrap();
diff --git a/crates/pink-libs/subrpc/src/objects.rs b/crates/pink-libs/subrpc/src/objects.rs
index 7518e9f54c..a3d5684dee 100644
--- a/crates/pink-libs/subrpc/src/objects.rs
+++ b/crates/pink-libs/subrpc/src/objects.rs
@@ -1,11 +1,48 @@
+use crate::primitives::digest::Digest;
 use alloc::borrow::ToOwned;
 use alloc::string::String;
 use alloc::vec::Vec;
 use scale::{Decode, Encode};
 use serde::Deserialize;
+use sp_core_hashing::blake2_256;
 
-mod era;
-pub use era::Era;
+#[allow(dead_code)]
+#[derive(Deserialize, Debug)]
+pub struct BlockHeader<'a> {
+    pub(crate) jsonrpc: &'a str,
+    #[serde(borrow)]
+    pub(crate) result: BlockHeaderResult<'a>,
+    pub(crate) id: u32,
+}
+
+#[derive(Deserialize, Encode, Clone, Debug, PartialEq)]
+pub struct BlockHeaderResult<'a> {
+    #[serde(alias = "parentHash")]
+    pub(crate) parent_hash: &'a str,
+    pub(crate) number: &'a str,
+    #[serde(alias = "stateRoot")]
+    pub(crate) state_root: &'a str,
+    #[serde(alias = "extrinsicsRoot")]
+    pub(crate) extrinsics_root: &'a str,
+    pub(crate) digest: Digest,
+}
+
+#[derive(Encode, Decode, Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub struct BlockHeaderOk {
+    pub(crate) parent_hash: [u8; 32],
+    #[codec(compact)]
+    pub(crate) number: u32,
+    pub(crate) state_root: [u8; 32],
+    pub(crate) extrinsics_root: [u8; 32],
+    pub(crate) digest: Digest,
+}
+
+impl BlockHeaderOk {
+    pub fn hash(&self) -> [u8; 32] {
+        blake2_256(&self.encode())
+    }
+}
 
 #[derive(Deserialize, Encode, Clone, Debug, PartialEq)]
 pub struct NextNonce<'a> {
@@ -78,15 +115,6 @@ pub struct TransactionResponse<'a> {
     pub(crate) id: u32,
 }
 
-#[derive(Encode, Decode, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
-pub struct ExtraParam {
-    // 0 if Immortal, or Vec<u64, u64> for period and the phase.
-    era: Era,
-    // Tip for the block producer.
-    tip: u128,
-}
-
 #[derive(Deserialize, Encode, Clone, Debug, PartialEq)]
 pub struct GetStorageResponse<'a> {
     pub(crate) jsonrpc: &'a str,
diff --git a/crates/pink-libs/subrpc/src/primitives/digest.rs b/crates/pink-libs/subrpc/src/primitives/digest.rs
new file mode 100644
index 0000000000..d06eea7b5e
--- /dev/null
+++ b/crates/pink-libs/subrpc/src/primitives/digest.rs
@@ -0,0 +1,443 @@
+#[cfg(feature = "std")]
+pub use impl_serde::serialize as bytes;
+use scale::{Decode, Encode};
+use scale_info::{
+    build::{Fields, Variants},
+    Path, Type, TypeInfo,
+};
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+
+/// Consensus engine unique ID.
+pub type ConsensusEngineId = [u8; 4];
+
+/// Generic header digest.
+#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Default)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, TypeInfo))]
+pub struct Digest {
+    /// A list of logs in the digest.
+    pub logs: Vec<DigestItem>,
+}
+
+impl Digest {
+    /// Get reference to all digest items.
+    #[allow(dead_code)]
+    pub fn logs(&self) -> &[DigestItem] {
+        &self.logs
+    }
+
+    /// Push new digest item.
+    #[allow(dead_code)]
+    pub fn push(&mut self, item: DigestItem) {
+        self.logs.push(item);
+    }
+
+    /// Pop a digest item.
+    #[allow(dead_code)]
+    pub fn pop(&mut self) -> Option<DigestItem> {
+        self.logs.pop()
+    }
+
+    /// Get reference to the first digest item that matches the passed predicate.
+    #[allow(dead_code)]
+    pub fn log<T: ?Sized, F: Fn(&DigestItem) -> Option<&T>>(&self, predicate: F) -> Option<&T> {
+        self.logs().iter().find_map(predicate)
+    }
+
+    /// Get a conversion of the first digest item that successfully converts using the function.
+    #[allow(dead_code)]
+    pub fn convert_first<T, F: Fn(&DigestItem) -> Option<T>>(&self, predicate: F) -> Option<T> {
+        self.logs().iter().find_map(predicate)
+    }
+}
+
+/// Digest item that is able to encode/decode 'system' digest items and
+/// provide opaque access to other items.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum DigestItem {
+    /// A pre-runtime digest.
+    ///
+    /// These are messages from the consensus engine to the runtime, although
+    /// the consensus engine can (and should) read them itself to avoid
+    /// code and state duplication. It is erroneous for a runtime to produce
+    /// these, but this is not (yet) checked.
+    ///
+    /// NOTE: the runtime is not allowed to panic or fail in an `on_initialize`
+    /// call if an expected `PreRuntime` digest is not present. It is the
+    /// responsibility of a external block verifier to check this. Runtime API calls
+    /// will initialize the block without pre-runtime digests, so initialization
+    /// cannot fail when they are missing.
+    PreRuntime(ConsensusEngineId, Vec<u8>),
+
+    /// A message from the runtime to the consensus engine. This should *never*
+    /// be generated by the native code of any consensus engine, but this is not
+    /// checked (yet).
+    Consensus(ConsensusEngineId, Vec<u8>),
+
+    /// Put a Seal on it. This is only used by native code, and is never seen
+    /// by runtimes.
+    Seal(ConsensusEngineId, Vec<u8>),
+
+    /// Some other thing. Unsupported and experimental.
+    Other(Vec<u8>),
+
+    /// An indication for the light clients that the runtime execution
+    /// environment is updated.
+    ///
+    /// Currently this is triggered when:
+    /// 1. Runtime code blob is changed or
+    /// 2. `heap_pages` value is changed.
+    RuntimeEnvironmentUpdated,
+}
+
+#[cfg(feature = "std")]
+impl serde::Serialize for DigestItem {
+    fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        self.using_encoded(|bytes| bytes::serialize(bytes, seq))
+    }
+}
+
+#[cfg(feature = "std")]
+impl<'a> serde::Deserialize<'a> for DigestItem {
+    fn deserialize<D>(de: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'a>,
+    {
+        let r = bytes::deserialize(de)?;
+        Decode::decode(&mut &r[..])
+            .map_err(|e| serde::de::Error::custom(format!("Decode error: {e}")))
+    }
+}
+
+#[cfg(feature = "std")]
+impl TypeInfo for DigestItem {
+    type Identity = Self;
+
+    fn type_info() -> Type {
+        Type::builder()
+            .path(Path::new("DigestItem", module_path!()))
+            .variant(
+                Variants::new()
+                    .variant("PreRuntime", |v| {
+                        v.index(DigestItemType::PreRuntime as u8).fields(
+                            Fields::unnamed()
+                                .field(|f| {
+                                    f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId")
+                                })
+                                .field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
+                        )
+                    })
+                    .variant("Consensus", |v| {
+                        v.index(DigestItemType::Consensus as u8).fields(
+                            Fields::unnamed()
+                                .field(|f| {
+                                    f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId")
+                                })
+                                .field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
+                        )
+                    })
+                    .variant("Seal", |v| {
+                        v.index(DigestItemType::Seal as u8).fields(
+                            Fields::unnamed()
+                                .field(|f| {
+                                    f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId")
+                                })
+                                .field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
+                        )
+                    })
+                    .variant("Other", |v| {
+                        v.index(DigestItemType::Other as u8).fields(
+                            Fields::unnamed().field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
+                        )
+                    })
+                    .variant("RuntimeEnvironmentUpdated", |v| {
+                        v.index(DigestItemType::RuntimeEnvironmentUpdated as u8)
+                            .fields(Fields::unit())
+                    }),
+            )
+    }
+}
+
+/// A 'referencing view' for digest item. Does not own its contents. Used by
+/// final runtime implementations for encoding/decoding its log items.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum DigestItemRef<'a> {
+    /// A pre-runtime digest.
+    ///
+    /// These are messages from the consensus engine to the runtime, although
+    /// the consensus engine can (and should) read them itself to avoid
+    /// code and state duplication.  It is erroneous for a runtime to produce
+    /// these, but this is not (yet) checked.
+    PreRuntime(&'a ConsensusEngineId, &'a [u8]),
+    /// A message from the runtime to the consensus engine. This should *never*
+    /// be generated by the native code of any consensus engine, but this is not
+    /// checked (yet).
+    Consensus(&'a ConsensusEngineId, &'a [u8]),
+    /// Put a Seal on it. This is only used by native code, and is never seen
+    /// by runtimes.
+    Seal(&'a ConsensusEngineId, &'a [u8]),
+    /// Any 'non-system' digest item, opaque to the native code.
+    Other(&'a [u8]),
+    /// Runtime code or heap pages updated.
+    RuntimeEnvironmentUpdated,
+}
+
+/// Type of the digest item. Used to gain explicit control over `DigestItem` encoding
+/// process. We need an explicit control, because final runtimes are encoding their own
+/// digest items using `DigestItemRef` type and we can't auto-derive `Decode`
+/// trait for `DigestItemRef`.
+#[repr(u32)]
+#[derive(Debug, Encode, Decode)]
+pub enum DigestItemType {
+    Other = 0u32,
+    Consensus = 4u32,
+    Seal = 5u32,
+    PreRuntime = 6u32,
+    RuntimeEnvironmentUpdated = 8u32,
+}
+
+/// Type of a digest item that contains raw data; this also names the consensus engine ID where
+/// applicable. Used to identify one or more digest items of interest.
+#[derive(Copy, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+#[allow(dead_code)]
+pub enum OpaqueDigestItemId<'a> {
+    /// Type corresponding to DigestItem::PreRuntime.
+    PreRuntime(&'a ConsensusEngineId),
+    /// Type corresponding to DigestItem::Consensus.
+    Consensus(&'a ConsensusEngineId),
+    /// Type corresponding to DigestItem::Seal.
+    Seal(&'a ConsensusEngineId),
+    /// Some other (non-prescribed) type.
+    Other,
+}
+
+impl DigestItem {
+    /// Returns a 'referencing view' for this digest item.
+    pub fn dref(&self) -> DigestItemRef {
+        match *self {
+            Self::PreRuntime(ref v, ref s) => DigestItemRef::PreRuntime(v, s),
+            Self::Consensus(ref v, ref s) => DigestItemRef::Consensus(v, s),
+            Self::Seal(ref v, ref s) => DigestItemRef::Seal(v, s),
+            Self::Other(ref v) => DigestItemRef::Other(v),
+            Self::RuntimeEnvironmentUpdated => DigestItemRef::RuntimeEnvironmentUpdated,
+        }
+    }
+
+    /// Returns `Some` if this entry is the `PreRuntime` entry.
+    #[allow(dead_code)]
+    pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &[u8])> {
+        self.dref().as_pre_runtime()
+    }
+
+    /// Returns `Some` if this entry is the `Consensus` entry.
+    #[allow(dead_code)]
+    pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &[u8])> {
+        self.dref().as_consensus()
+    }
+
+    /// Returns `Some` if this entry is the `Seal` entry.
+    #[allow(dead_code)]
+    pub fn as_seal(&self) -> Option<(ConsensusEngineId, &[u8])> {
+        self.dref().as_seal()
+    }
+
+    /// Returns Some if `self` is a `DigestItem::Other`.
+    #[allow(dead_code)]
+    pub fn as_other(&self) -> Option<&[u8]> {
+        self.dref().as_other()
+    }
+
+    /// Returns the opaque data contained in the item if `Some` if this entry has the id given.
+    #[allow(dead_code)]
+    pub fn try_as_raw(&self, id: OpaqueDigestItemId) -> Option<&[u8]> {
+        self.dref().try_as_raw(id)
+    }
+
+    /// Returns the data contained in the item if `Some` if this entry has the id given, decoded
+    /// to the type provided `T`.
+    #[allow(dead_code)]
+    pub fn try_to<T: Decode>(&self, id: OpaqueDigestItemId) -> Option<T> {
+        self.dref().try_to::<T>(id)
+    }
+
+    /// Try to match this to a `Self::Seal`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a seal item, the `id` doesn't match or when the decoding fails.
+    #[allow(dead_code)]
+    pub fn seal_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        self.dref().seal_try_to(id)
+    }
+
+    /// Try to match this to a `Self::Consensus`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a consensus item, the `id` doesn't match or
+    /// when the decoding fails.
+    #[allow(dead_code)]
+    pub fn consensus_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        self.dref().consensus_try_to(id)
+    }
+
+    /// Try to match this to a `Self::PreRuntime`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a pre-runtime item, the `id` doesn't match or
+    /// when the decoding fails.
+    #[allow(dead_code)]
+    pub fn pre_runtime_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        self.dref().pre_runtime_try_to(id)
+    }
+}
+
+impl Encode for DigestItem {
+    fn encode(&self) -> Vec<u8> {
+        self.dref().encode()
+    }
+}
+
+impl scale::EncodeLike for DigestItem {}
+
+impl Decode for DigestItem {
+    #[allow(deprecated)]
+    fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
+        let item_type: DigestItemType = Decode::decode(input)?;
+        match item_type {
+            DigestItemType::PreRuntime => {
+                let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
+                Ok(Self::PreRuntime(vals.0, vals.1))
+            }
+            DigestItemType::Consensus => {
+                let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
+                Ok(Self::Consensus(vals.0, vals.1))
+            }
+            DigestItemType::Seal => {
+                let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
+                Ok(Self::Seal(vals.0, vals.1))
+            }
+            DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)),
+            DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated),
+        }
+    }
+}
+
+impl<'a> DigestItemRef<'a> {
+    /// Cast this digest item into `PreRuntime`
+    pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
+        match *self {
+            Self::PreRuntime(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
+            _ => None,
+        }
+    }
+
+    /// Cast this digest item into `Consensus`
+    pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
+        match *self {
+            Self::Consensus(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
+            _ => None,
+        }
+    }
+
+    /// Cast this digest item into `Seal`
+    pub fn as_seal(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
+        match *self {
+            Self::Seal(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
+            _ => None,
+        }
+    }
+
+    /// Cast this digest item into `PreRuntime`
+    pub fn as_other(&self) -> Option<&'a [u8]> {
+        match *self {
+            Self::Other(data) => Some(data),
+            _ => None,
+        }
+    }
+
+    /// Try to match this digest item to the given opaque item identifier; if it matches, then
+    /// return the opaque data it contains.
+    pub fn try_as_raw(&self, id: OpaqueDigestItemId) -> Option<&'a [u8]> {
+        match (id, self) {
+            (OpaqueDigestItemId::Consensus(w), &Self::Consensus(v, s))
+            | (OpaqueDigestItemId::Seal(w), &Self::Seal(v, s))
+            | (OpaqueDigestItemId::PreRuntime(w), &Self::PreRuntime(v, s))
+                if v == w =>
+            {
+                Some(s)
+            }
+            (OpaqueDigestItemId::Other, &Self::Other(s)) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Try to match this digest item to the given opaque item identifier; if it matches, then
+    /// try to cast to the given data type; if that works, return it.
+    pub fn try_to<T: Decode>(&self, id: OpaqueDigestItemId) -> Option<T> {
+        self.try_as_raw(id)
+            .and_then(|mut x| Decode::decode(&mut x).ok())
+    }
+
+    /// Try to match this to a `Self::Seal`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a seal item, the `id` doesn't match or when the decoding fails.
+    pub fn seal_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        match self {
+            Self::Seal(v, s) if *v == id => Decode::decode(&mut &s[..]).ok(),
+            _ => None,
+        }
+    }
+
+    /// Try to match this to a `Self::Consensus`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a consensus item, the `id` doesn't match or
+    /// when the decoding fails.
+    pub fn consensus_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        match self {
+            Self::Consensus(v, s) if *v == id => Decode::decode(&mut &s[..]).ok(),
+            _ => None,
+        }
+    }
+
+    /// Try to match this to a `Self::PreRuntime`, check `id` matches and decode it.
+    ///
+    /// Returns `None` if this isn't a pre-runtime item, the `id` doesn't match or
+    /// when the decoding fails.
+    pub fn pre_runtime_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
+        match self {
+            Self::PreRuntime(v, s) if *v == id => Decode::decode(&mut &s[..]).ok(),
+            _ => None,
+        }
+    }
+}
+
+impl<'a> Encode for DigestItemRef<'a> {
+    fn encode(&self) -> Vec<u8> {
+        let mut v = Vec::new();
+
+        match *self {
+            Self::Consensus(val, data) => {
+                DigestItemType::Consensus.encode_to(&mut v);
+                (val, data).encode_to(&mut v);
+            }
+            Self::Seal(val, sig) => {
+                DigestItemType::Seal.encode_to(&mut v);
+                (val, sig).encode_to(&mut v);
+            }
+            Self::PreRuntime(val, data) => {
+                DigestItemType::PreRuntime.encode_to(&mut v);
+                (val, data).encode_to(&mut v);
+            }
+            Self::Other(val) => {
+                DigestItemType::Other.encode_to(&mut v);
+                val.encode_to(&mut v);
+            }
+            Self::RuntimeEnvironmentUpdated => {
+                DigestItemType::RuntimeEnvironmentUpdated.encode_to(&mut v);
+            }
+        }
+
+        v
+    }
+}
+
+impl<'a> scale::EncodeLike for DigestItemRef<'a> {}
diff --git a/crates/pink-libs/subrpc/src/objects/era.rs b/crates/pink-libs/subrpc/src/primitives/era.rs
similarity index 100%
rename from crates/pink-libs/subrpc/src/objects/era.rs
rename to crates/pink-libs/subrpc/src/primitives/era.rs
diff --git a/crates/pink-libs/subrpc/src/primitives/mod.rs b/crates/pink-libs/subrpc/src/primitives/mod.rs
new file mode 100644
index 0000000000..3a7fd6826d
--- /dev/null
+++ b/crates/pink-libs/subrpc/src/primitives/mod.rs
@@ -0,0 +1,2 @@
+pub mod digest;
+pub mod era;


Test patch: