From 545eb77193398abfc4110b4624e649f8f3c806d2 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Thu, 9 Mar 2023 12:56:07 +0200 Subject: [PATCH] Some error improvements (#1956) * Use `HeaderChainError` in parachains module * Use MessageProofError instead of 'static str in some places * Avoid implementing Into<'static str> for some errors We avoid deriving `Debug` for the structs that we use in the runtime and we derive `RuntimeDebug` instead in order to avoid bloating th eruntime with static strs. But implementing `Into<'static str>` does the same. So in some places it makes sense to replace `Into<'static str>` with `Debug`. * Move the messages error definition Move the messages error definition outside of `mod target` --- bridges/bin/runtime-common/src/messages.rs | 170 ++++++++---------- bridges/modules/parachains/src/lib.rs | 14 +- bridges/primitives/header-chain/src/lib.rs | 12 +- .../primitives/messages/src/source_chain.rs | 2 +- .../primitives/messages/src/target_chain.rs | 2 +- bridges/primitives/runtime/src/lib.rs | 31 ++++ .../primitives/runtime/src/storage_proof.rs | 46 +++-- 7 files changed, 145 insertions(+), 132 deletions(-) diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index ceedee12afabf..4eb44ea85ed45 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -100,6 +100,29 @@ pub type OriginOf = ::RuntimeOrigin; /// Type of call that is used on this chain. pub type CallOf = ::RuntimeCall; +/// Error that happens during message verification. +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// The message proof is empty. + EmptyMessageProof, + /// Error returned by the bridged header chain. + HeaderChain(HeaderChainError), + /// Error returned while reading/decoding inbound lane data from the storage proof. + InboundLaneStorage(StorageProofError), + /// The declared message weight is incorrect. + InvalidMessageWeight, + /// Declared messages count doesn't match actual value. + MessagesCountMismatch, + /// Error returned while reading/decoding message data from the storage proof. + MessageStorage(StorageProofError), + /// The message is too large. + MessageTooLarge, + /// Error returned while reading/decoding outbound lane data from the storage proof. + OutboundLaneStorage(StorageProofError), + /// Storage proof related error. + StorageProof(StorageProofError), +} + /// Sub-module that is declaring types required for processing This -> Bridged chain messages. pub mod source { use super::*; @@ -169,8 +192,6 @@ pub mod source { /// The error message returned from `LaneMessageVerifier` when too many pending messages at the /// lane. pub const TOO_MANY_PENDING_MESSAGES: &str = "Too many pending messages at the lane."; - /// The error message returned from `LaneMessageVerifier` when call origin is mismatch. - pub const BAD_ORIGIN: &str = "Unable to match the source origin to expected target origin."; impl LaneMessageVerifier>, FromThisChainMessagePayload> for FromThisChainMessageVerifier @@ -220,7 +241,7 @@ pub mod source { impl TargetHeaderChain>> for TargetHeaderChainAdapter { - type Error = &'static str; + type Error = Error; type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof>>; fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), Self::Error> { @@ -241,9 +262,9 @@ pub mod source { /// check) that would reject message (see `FromThisChainMessageVerifier`). pub fn verify_chain_message( payload: &FromThisChainMessagePayload, - ) -> Result<(), &'static str> { + ) -> Result<(), Error> { if !BridgedChain::::verify_dispatch_weight(payload) { - return Err("Incorrect message weight declared") + return Err(Error::InvalidMessageWeight) } // The maximal size of extrinsic at Substrate-based chain depends on the @@ -257,7 +278,7 @@ pub mod source { // transaction also contains signatures and signed extensions. Because of this, we reserve // 1/3 of the the maximal extrinsic weight for this data. if payload.len() > maximal_message_size::() as usize { - return Err("The message is too large to be sent over the lane") + return Err(Error::MessageTooLarge) } Ok(()) @@ -269,7 +290,7 @@ pub mod source { /// parachains, please use the `verify_messages_delivery_proof_from_parachain`. pub fn verify_messages_delivery_proof( proof: FromBridgedChainMessagesDeliveryProof>>, - ) -> Result, &'static str> { + ) -> Result, Error> { let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; B::BridgedHeaderChain::parse_finalized_storage_proof( @@ -283,22 +304,17 @@ pub mod source { B::BRIDGED_MESSAGES_PALLET_NAME, &lane, ); - let raw_inbound_lane_data = storage - .read_value(storage_inbound_lane_data_key.0.as_ref()) - .map_err(|_| "Failed to read inbound lane state from storage proof")? - .ok_or("Inbound lane state is missing from the messages proof")?; - let inbound_lane_data = InboundLaneData::decode(&mut &raw_inbound_lane_data[..]) - .map_err(|_| "Failed to decode inbound lane state from the proof")?; + let inbound_lane_data = storage + .read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref()) + .map_err(Error::InboundLaneStorage)?; // check that the storage proof doesn't have any untouched trie nodes - storage - .ensure_no_unused_nodes() - .map_err(|_| "Messages delivery proof has unused trie nodes")?; + storage.ensure_no_unused_nodes().map_err(Error::StorageProof)?; Ok((lane, inbound_lane_data)) }, ) - .map_err(<&'static str>::from)? + .map_err(Error::HeaderChain)? } /// XCM bridge. @@ -580,14 +596,14 @@ pub mod target { pub struct SourceHeaderChainAdapter(PhantomData); impl SourceHeaderChain for SourceHeaderChainAdapter { - type Error = &'static str; + type Error = Error; type MessagesProof = FromBridgedChainMessagesProof>>; fn verify_messages_proof( proof: Self::MessagesProof, messages_count: u32, ) -> Result, Self::Error> { - verify_messages_proof::(proof, messages_count).map_err(Into::into) + verify_messages_proof::(proof, messages_count) } } @@ -602,7 +618,7 @@ pub mod target { pub fn verify_messages_proof( proof: FromBridgedChainMessagesProof>>, messages_count: u32, - ) -> Result, MessageProofError> { + ) -> Result, Error> { let FromBridgedChainMessagesProof { bridged_header_hash, storage_proof, @@ -625,7 +641,7 @@ pub mod target { // (this bounds maximal capacity of messages vec below) let messages_in_the_proof = nonces_difference.saturating_add(1); if messages_in_the_proof != MessageNonce::from(messages_count) { - return Err(MessageProofError::MessagesCountMismatch) + return Err(Error::MessagesCountMismatch) } messages_in_the_proof @@ -640,37 +656,26 @@ pub mod target { let mut messages = Vec::with_capacity(messages_in_the_proof as _); for nonce in nonces_start..=nonces_end { let message_key = MessageKey { lane_id: lane, nonce }; - let raw_message_data = parser - .read_raw_message(&message_key) - .ok_or(MessageProofError::MissingRequiredMessage)?; - let payload = MessagePayload::decode(&mut &raw_message_data[..]) - .map_err(|_| MessageProofError::FailedToDecodeMessage)?; - messages.push(Message { key: message_key, payload }); + let message_payload = parser.read_and_decode_message_payload(&message_key)?; + messages.push(Message { key: message_key, payload: message_payload }); } // Now let's check if proof contains outbound lane state proof. It is optional, so // we simply ignore `read_value` errors and missing value. - let mut proved_lane_messages = ProvedLaneMessages { lane_state: None, messages }; - let raw_outbound_lane_data = parser.read_raw_outbound_lane_data(&lane); - if let Some(raw_outbound_lane_data) = raw_outbound_lane_data { - proved_lane_messages.lane_state = Some( - OutboundLaneData::decode(&mut &raw_outbound_lane_data[..]) - .map_err(|_| MessageProofError::FailedToDecodeOutboundLaneState)?, - ); - } + let proved_lane_messages = ProvedLaneMessages { + lane_state: parser.read_and_decode_outbound_lane_data(&lane)?, + messages, + }; // Now we may actually check if the proof is empty or not. if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() { - return Err(MessageProofError::Empty) + return Err(Error::EmptyMessageProof) } // check that the storage proof doesn't have any untouched trie nodes - parser - .storage - .ensure_no_unused_nodes() - .map_err(MessageProofError::StorageProof)?; + parser.storage.ensure_no_unused_nodes().map_err(Error::StorageProof)?; // We only support single lane messages in this generated_schema let mut proved_messages = ProvedMessages::new(); @@ -679,43 +684,7 @@ pub mod target { Ok(proved_messages) }, ) - .map_err(MessageProofError::HeaderChain)? - } - - /// Error that happens during message proof verification. - #[derive(Debug, PartialEq, Eq)] - pub enum MessageProofError { - /// Error returned by the bridged header chain. - HeaderChain(HeaderChainError), - /// The message proof is empty. - Empty, - /// Declared messages count doesn't match actual value. - MessagesCountMismatch, - /// Message is missing from the proof. - MissingRequiredMessage, - /// Failed to decode message from the proof. - FailedToDecodeMessage, - /// Failed to decode outbound lane data from the proof. - FailedToDecodeOutboundLaneState, - /// Storage proof related error. - StorageProof(StorageProofError), - } - - impl From for &'static str { - fn from(err: MessageProofError) -> &'static str { - match err { - MessageProofError::HeaderChain(err) => err.into(), - MessageProofError::Empty => "Messages proof is empty", - MessageProofError::MessagesCountMismatch => - "Declared messages count doesn't match actual value", - MessageProofError::MissingRequiredMessage => "Message is missing from the proof", - MessageProofError::FailedToDecodeMessage => - "Failed to decode message from the proof", - MessageProofError::FailedToDecodeOutboundLaneState => - "Failed to decode outbound lane data from the proof", - MessageProofError::StorageProof(_) => "Invalid storage proof", - } - } + .map_err(Error::HeaderChain)? } struct StorageProofCheckerAdapter { @@ -724,21 +693,32 @@ pub mod target { } impl StorageProofCheckerAdapter { - fn read_raw_outbound_lane_data(&mut self, lane_id: &LaneId) -> Option> { + fn read_and_decode_outbound_lane_data( + &mut self, + lane_id: &LaneId, + ) -> Result, Error> { let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( B::BRIDGED_MESSAGES_PALLET_NAME, lane_id, ); - self.storage.read_value(storage_outbound_lane_data_key.0.as_ref()).ok()? + + self.storage + .read_and_decode_opt_value(storage_outbound_lane_data_key.0.as_ref()) + .map_err(Error::OutboundLaneStorage) } - fn read_raw_message(&mut self, message_key: &MessageKey) -> Option> { + fn read_and_decode_message_payload( + &mut self, + message_key: &MessageKey, + ) -> Result { let storage_message_key = bp_messages::storage_keys::message_key( B::BRIDGED_MESSAGES_PALLET_NAME, &message_key.lane_id, message_key.nonce, ); - self.storage.read_value(storage_message_key.0.as_ref()).ok()? + self.storage + .read_and_decode_mandatory_value(storage_message_key.0.as_ref()) + .map_err(Error::MessageStorage) } } } @@ -896,7 +876,7 @@ mod tests { using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { target::verify_messages_proof::(proof, 5) }), - Err(target::MessageProofError::MessagesCountMismatch), + Err(Error::MessagesCountMismatch), ); } @@ -906,7 +886,7 @@ mod tests { using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| { target::verify_messages_proof::(proof, 15) }), - Err(target::MessageProofError::MessagesCountMismatch), + Err(Error::MessagesCountMismatch), ); } @@ -919,7 +899,7 @@ mod tests { pallet_bridge_grandpa::ImportedHeaders::::remove(bridged_header_hash); target::verify_messages_proof::(proof, 10) }), - Err(target::MessageProofError::HeaderChain(HeaderChainError::UnknownHeader)), + Err(Error::HeaderChain(HeaderChainError::UnknownHeader)), ); } @@ -942,7 +922,7 @@ mod tests { ); target::verify_messages_proof::(proof, 10) }), - Err(target::MessageProofError::HeaderChain(HeaderChainError::StorageProof( + Err(Error::HeaderChain(HeaderChainError::StorageProof( StorageProofError::StorageRootMismatch ))), ); @@ -957,7 +937,7 @@ mod tests { proof.storage_proof.push(node); target::verify_messages_proof::(proof, 10) },), - Err(target::MessageProofError::HeaderChain(HeaderChainError::StorageProof( + Err(Error::HeaderChain(HeaderChainError::StorageProof( StorageProofError::DuplicateNodesInProof ))), ); @@ -970,13 +950,13 @@ mod tests { proof.storage_proof.push(vec![42]); target::verify_messages_proof::(proof, 10) },), - Err(target::MessageProofError::StorageProof(StorageProofError::UnusedNodesInTheProof)), + Err(Error::StorageProof(StorageProofError::UnusedNodesInTheProof)), ); } #[test] fn message_proof_is_rejected_if_required_message_is_missing() { - assert_eq!( + matches!( using_messages_proof( 10, None, @@ -984,13 +964,13 @@ mod tests { encode_lane_data, |proof| target::verify_messages_proof::(proof, 10) ), - Err(target::MessageProofError::MissingRequiredMessage), + Err(Error::MessageStorage(StorageProofError::StorageValueEmpty)), ); } #[test] fn message_proof_is_rejected_if_message_decode_fails() { - assert_eq!( + matches!( using_messages_proof( 10, None, @@ -1004,13 +984,13 @@ mod tests { encode_lane_data, |proof| target::verify_messages_proof::(proof, 10), ), - Err(target::MessageProofError::FailedToDecodeMessage), + Err(Error::MessageStorage(StorageProofError::StorageValueDecodeFailed(_))), ); } #[test] fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() { - assert_eq!( + matches!( using_messages_proof( 10, Some(OutboundLaneData { @@ -1026,7 +1006,7 @@ mod tests { }, |proof| target::verify_messages_proof::(proof, 10), ), - Err(target::MessageProofError::FailedToDecodeOutboundLaneState), + Err(Error::OutboundLaneStorage(StorageProofError::StorageValueDecodeFailed(_))), ); } @@ -1036,7 +1016,7 @@ mod tests { using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| { target::verify_messages_proof::(proof, 0) },), - Err(target::MessageProofError::Empty), + Err(Error::EmptyMessageProof), ); } @@ -1110,7 +1090,7 @@ mod tests { proof.nonces_end = u64::MAX; target::verify_messages_proof::(proof, u32::MAX) },), - Err(target::MessageProofError::MessagesCountMismatch), + Err(Error::MessagesCountMismatch), ); } } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index b9bbaac7ca3d4..98556f737bac3 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -26,7 +26,7 @@ pub use weights::WeightInfo; pub use weights_ext::WeightInfoExt; -use bp_header_chain::HeaderChain; +use bp_header_chain::{HeaderChain, HeaderChainError}; use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo, ParaStoredHeaderData}; use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain, StorageProofError}; @@ -125,8 +125,8 @@ pub mod pallet { UnknownRelayChainBlock, /// The number of stored relay block is different from what the relayer has provided. InvalidRelayChainBlockNumber, - /// Invalid storage proof has been passed. - InvalidStorageProof, + /// Error generated by a method defined in `bp-header-chain`. + HeaderChain(HeaderChainError), /// Given parachain head is unknown. UnknownParaHead, /// The storage proof doesn't contains storage root. So it is invalid for given header. @@ -430,10 +430,10 @@ pub mod pallet { storage.ensure_no_unused_nodes() }, ) - .and_then(|r| r.map_err(bp_header_chain::HeaderChainError::StorageProof)) + .and_then(|r| r.map_err(HeaderChainError::StorageProof)) .map_err(|e| { log::trace!(target: LOG_TARGET, "Parachain heads storage proof is invalid: {:?}", e); - Error::::InvalidStorageProof + Error::::HeaderChain(e) })?; Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) @@ -1379,7 +1379,9 @@ mod tests { // try to import head#5 of parachain#1 at relay chain block #0 assert_noop!( import_parachain_1_head(0, Default::default(), parachains, proof), - Error::::InvalidStorageProof + Error::::HeaderChain(HeaderChainError::StorageProof( + StorageProofError::StorageRootMismatch + )) ); }); } diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index cafb8e7a3c86f..5d05a1b15d8fa 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -25,6 +25,7 @@ use bp_runtime::{ }; use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug}; +use frame_support::PalletError; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -36,7 +37,7 @@ pub mod justification; pub mod storage_keys; /// Header chain error. -#[derive(Clone, Eq, PartialEq, RuntimeDebug)] +#[derive(Clone, Decode, Encode, Eq, PartialEq, PalletError, Debug, TypeInfo)] pub enum HeaderChainError { /// Header with given hash is missing from the chain. UnknownHeader, @@ -44,15 +45,6 @@ pub enum HeaderChainError { StorageProof(StorageProofError), } -impl From for &'static str { - fn from(err: HeaderChainError) -> &'static str { - match err { - HeaderChainError::UnknownHeader => "UnknownHeader", - HeaderChainError::StorageProof(e) => e.into(), - } - } -} - /// Header data that we're storing on-chain. /// /// Even though we may store full header, our applications (XCM) only use couple of header diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index d5d7549099c31..881bd92f5f955 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -41,7 +41,7 @@ pub type RelayersRewards = BTreeMap; /// type used by the source chain. pub trait TargetHeaderChain { /// Error type. - type Error: Debug + Into<&'static str>; + type Error: Debug; /// Proof that messages have been received by target chain. type MessagesDeliveryProof: Parameter + Size; diff --git a/bridges/primitives/messages/src/target_chain.rs b/bridges/primitives/messages/src/target_chain.rs index 6604d425cfc4a..8496b90214c40 100644 --- a/bridges/primitives/messages/src/target_chain.rs +++ b/bridges/primitives/messages/src/target_chain.rs @@ -59,7 +59,7 @@ pub struct DispatchMessage { /// that's stuck) and/or processing messages without paying fees. pub trait SourceHeaderChain { /// Error type. - type Error: Debug + Into<&'static str>; + type Error: Debug; /// Proof that messages are sent from source chain. This may also include proof /// of corresponding outbound lane states. diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 2d29b5aff0ab9..ece925d795273 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -531,6 +531,37 @@ macro_rules! generate_static_str_provider { }; } +#[derive(Encode, Decode, Clone, Eq, PartialEq, PalletError, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct StrippableError { + _phantom_data: sp_std::marker::PhantomData, + #[codec(skip)] + #[cfg(feature = "std")] + message: String, +} + +impl From for StrippableError { + fn from(err: T) -> Self { + Self { + _phantom_data: Default::default(), + #[cfg(feature = "std")] + message: format!("{:?}", err), + } + } +} + +impl Debug for StrippableError { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.write_str(&self.message) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.write_str("Stripped error") + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/bridges/primitives/runtime/src/storage_proof.rs b/bridges/primitives/runtime/src/storage_proof.rs index aa9c3088fa42f..09641376666af 100644 --- a/bridges/primitives/runtime/src/storage_proof.rs +++ b/bridges/primitives/runtime/src/storage_proof.rs @@ -16,9 +16,11 @@ //! Logic for checking Substrate storage proofs. -use codec::Decode; +use crate::StrippableError; +use codec::{Decode, Encode}; +use frame_support::PalletError; use hash_db::{HashDB, Hasher, EMPTY_PREFIX}; -use sp_runtime::RuntimeDebug; +use scale_info::TypeInfo; use sp_std::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec}; use sp_trie::{ read_trie_value, LayoutV1, MemoryDB, Recorder, StorageProof, Trie, TrieConfiguration, @@ -116,14 +118,32 @@ where /// read, but decoding fails, this function returns an error. pub fn read_and_decode_value(&mut self, key: &[u8]) -> Result, Error> { self.read_value(key).and_then(|v| { - v.map(|v| T::decode(&mut &v[..]).map_err(Error::StorageValueDecodeFailed)) + v.map(|v| T::decode(&mut &v[..]).map_err(|e| Error::StorageValueDecodeFailed(e.into()))) .transpose() }) } + + /// Reads and decodes a value from the available subset of storage. If the value cannot be read + /// due to an incomplete or otherwise invalid proof, or if the value is `None`, this function + /// returns an error. If value is read, but decoding fails, this function returns an error. + pub fn read_and_decode_mandatory_value(&mut self, key: &[u8]) -> Result { + self.read_and_decode_value(key)?.ok_or(Error::StorageValueEmpty) + } + + /// Reads and decodes a value from the available subset of storage. If the value cannot be read + /// due to an incomplete or otherwise invalid proof, this function returns `Ok(None)`. + /// If value is read, but decoding fails, this function returns an error. + pub fn read_and_decode_opt_value(&mut self, key: &[u8]) -> Result, Error> { + match self.read_and_decode_value(key) { + Ok(outbound_lane_data) => Ok(outbound_lane_data), + Err(Error::StorageValueUnavailable) => Ok(None), + Err(e) => Err(e), + } + } } /// Storage proof related errors. -#[derive(Clone, Eq, PartialEq, RuntimeDebug)] +#[derive(Encode, Decode, Clone, Eq, PartialEq, PalletError, Debug, TypeInfo)] pub enum Error { /// Duplicate trie nodes are found in the proof. DuplicateNodesInProof, @@ -133,21 +153,10 @@ pub enum Error { StorageRootMismatch, /// Unable to reach expected storage value using provided trie nodes. StorageValueUnavailable, + /// The storage value is `None`. + StorageValueEmpty, /// Failed to decode storage value. - StorageValueDecodeFailed(codec::Error), -} - -impl From for &'static str { - fn from(err: Error) -> &'static str { - match err { - Error::DuplicateNodesInProof => "Storage proof contains duplicate nodes", - Error::UnusedNodesInTheProof => "Storage proof contains unused nodes", - Error::StorageRootMismatch => "Storage root is missing from the storage proof", - Error::StorageValueUnavailable => "Storage value is missing from the storage proof", - Error::StorageValueDecodeFailed(_) => - "Failed to decode storage value from the storage proof", - } - } + StorageValueDecodeFailed(StrippableError), } /// Return valid storage proof and state root. @@ -155,7 +164,6 @@ impl From for &'static str { /// NOTE: This should only be used for **testing**. #[cfg(feature = "std")] pub fn craft_valid_storage_proof() -> (sp_core::H256, RawStorageProof) { - use codec::Encode; use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend}; let state_version = sp_runtime::StateVersion::default();