From 4cadb729bad80ddda400a2138a65ffdc2c9c06f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 25 Nov 2019 17:38:41 +0100 Subject: [PATCH 1/3] Check in block authoring that we can author with current authoring version --- Cargo.lock | 1 + bin/node-template/Cargo.toml | 1 + bin/node-template/src/service.rs | 6 ++- bin/node/cli/Cargo.toml | 2 +- bin/node/cli/src/service.rs | 4 ++ client/api/src/error.rs | 4 +- client/consensus/aura/src/lib.rs | 16 +++++--- client/consensus/babe/src/lib.rs | 14 +++++-- client/consensus/babe/src/tests.rs | 1 + client/consensus/pow/src/lib.rs | 24 ++++++++++-- client/consensus/slots/src/lib.rs | 32 ++++++++++----- client/executor/src/lib.rs | 5 +-- client/executor/src/native_executor.rs | 12 ++---- client/rpc/src/state/state_light.rs | 7 ++-- client/src/call_executor.rs | 28 ++++++++++--- client/src/client.rs | 7 ++-- primitives/consensus/common/src/lib.rs | 54 ++++++++++++++++++++++++-- primitives/sr-version/src/lib.rs | 24 ++++++++++++ 18 files changed, 187 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a8078053e021..0ca53fcf005a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3245,6 +3245,7 @@ dependencies = [ "substrate-client 2.0.0", "substrate-consensus-aura 2.0.0", "substrate-consensus-aura-primitives 2.0.0", + "substrate-consensus-common 2.0.0", "substrate-executor 2.0.0", "substrate-finality-grandpa 2.0.0", "substrate-finality-grandpa-primitives 2.0.0", diff --git a/bin/node-template/Cargo.toml b/bin/node-template/Cargo.toml index ff393730761bb..bb64d10ea5892 100644 --- a/bin/node-template/Cargo.toml +++ b/bin/node-template/Cargo.toml @@ -29,6 +29,7 @@ transaction-pool = { package = "substrate-transaction-pool", path = "../../clien network = { package = "substrate-network", path = "../../client/network" } aura = { package = "substrate-consensus-aura", path = "../../client/consensus/aura" } aura-primitives = { package = "substrate-consensus-aura-primitives", path = "../../primitives/consensus/aura" } +consensus-common = { package = "substrate-consensus-common", path = "../../primitives/consensus/common" } grandpa = { package = "substrate-finality-grandpa", path = "../../client/finality-grandpa" } grandpa-primitives = { package = "substrate-finality-grandpa-primitives", path = "../../primitives/finality-grandpa" } substrate-client = { path = "../../client/" } diff --git a/bin/node-template/src/service.rs b/bin/node-template/src/service.rs index ad517d956b4c7..2bfd090a71344 100644 --- a/bin/node-template/src/service.rs +++ b/bin/node-template/src/service.rs @@ -108,7 +108,10 @@ pub fn new_full(config: Configuration( + let can_author_with = + consensus_common::CanAuthorWithNativeVersion::new(client.executor().clone()); + + let aura = aura::start_aura::<_, _, _, _, _, AuraPair, _, _, _, _>( aura::SlotDuration::get_or_compute(&*client)?, client, select_chain, @@ -118,6 +121,7 @@ pub fn new_full(config: Configuration( +pub fn start_aura( slot_duration: SlotDuration, client: Arc, select_chain: SC, @@ -150,6 +150,7 @@ pub fn start_aura( inherent_data_providers: InherentDataProviders, force_authoring: bool, keystore: KeyStorePtr, + can_author_with: CAW, ) -> Result, consensus_common::Error> where B: BlockT, C: ProvideRuntimeApi + BlockOf + ProvideCache + AuxStore + Send + Sync, @@ -165,6 +166,7 @@ pub fn start_aura( I: BlockImport + Send + Sync + 'static, Error: ::std::error::Error + Send + From<::consensus_common::Error> + From + 'static, SO: SyncOracle + Send + Sync + Clone, + CAW: CanAuthorWith + Send, { let worker = AuraWorker { client: client.clone(), @@ -179,13 +181,14 @@ pub fn start_aura( &inherent_data_providers, slot_duration.0.slot_duration() )?; - Ok(slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible>( + Ok(slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible, _>( slot_duration.0, select_chain, worker, sync_oracle, inherent_data_providers, AuraSlotCompatible, + can_author_with, ).map(|()| Ok::<(), ()>(())).compat()) } @@ -859,7 +862,7 @@ mod tests { &inherent_data_providers, slot_duration.get() ).expect("Registers aura inherent data provider"); - let aura = start_aura::<_, _, _, _, _, AuthorityPair, _, _, _>( + let aura = start_aura::<_, _, _, _, _, AuthorityPair, _, _, _, _>( slot_duration, client.clone(), select_chain, @@ -869,6 +872,7 @@ mod tests { inherent_data_providers, false, keystore, + consensus_common::AlwaysCanAuthor, ).expect("Starts aura"); runtime.spawn(aura); diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 5d327bcedf7f6..20973a32205ff 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -65,7 +65,7 @@ pub use babe_primitives::{ pub use consensus_common::SyncOracle; use std::{collections::HashMap, sync::Arc, u64, pin::Pin, time::{Instant, Duration}}; use babe_primitives; -use consensus_common::ImportResult; +use consensus_common::{ImportResult, CanAuthorWith}; use consensus_common::import_queue::{ BoxJustificationImport, BoxFinalityProofImport, }; @@ -240,7 +240,7 @@ impl std::ops::Deref for Config { } /// Parameters for BABE. -pub struct BabeParams { +pub struct BabeParams { /// The keystore that manages the keys of the node. pub keystore: KeyStorePtr, @@ -269,10 +269,13 @@ pub struct BabeParams { /// The source of timestamps for relative slots pub babe_link: BabeLink, + + /// Checks if the current native implementation can author with a runtime at a given block. + pub can_author_with: CAW, } /// Start the babe worker. The returned future should be run in a tokio runtime. -pub fn start_babe(BabeParams { +pub fn start_babe(BabeParams { keystore, client, select_chain, @@ -282,7 +285,8 @@ pub fn start_babe(BabeParams { inherent_data_providers, force_authoring, babe_link, -}: BabeParams) -> Result< + can_author_with, +}: BabeParams) -> Result< impl futures01::Future, consensus_common::Error, > where @@ -297,6 +301,7 @@ pub fn start_babe(BabeParams { I: BlockImport + Send + Sync + 'static, Error: std::error::Error + Send + From<::consensus_common::Error> + From + 'static, SO: SyncOracle + Send + Sync + Clone, + CAW: CanAuthorWith + Send, { let config = babe_link.config; let worker = BabeWorker { @@ -325,6 +330,7 @@ pub fn start_babe(BabeParams { sync_oracle, inherent_data_providers, babe_link.time_source, + can_author_with, ); Ok(slot_worker.map(|_| Ok::<(), ()>(())).compat()) diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 19c5844511288..287b7f756e619 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -404,6 +404,7 @@ fn run_one_test( force_authoring: false, babe_link: data.link.clone(), keystore, + can_author_with: consensus_common::AlwaysCanAuthor, }).expect("Starts babe")); } diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index cb3f23fbf57dc..6da688f756ee8 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -46,7 +46,7 @@ use primitives::H256; use inherents::{InherentDataProviders, InherentData}; use consensus_common::{ BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer, - SelectChain, Error as ConsensusError + SelectChain, Error as ConsensusError, CanAuthorWith, }; use consensus_common::import_queue::{BoxBlockImport, BasicQueue, Verifier}; use codec::{Encode, Decode}; @@ -373,7 +373,7 @@ pub fn import_queue( /// information, or just be a graffiti. `round` is for number of rounds the /// CPU miner runs each time. This parameter should be tweaked so that each /// mining round is within sub-second time. -pub fn start_mine, C, Algorithm, E, SO, S>( +pub fn start_mine, C, Algorithm, E, SO, S, CAW>( mut block_import: BoxBlockImport, client: Arc, algorithm: Algorithm, @@ -384,6 +384,7 @@ pub fn start_mine, C, Algorithm, E, SO, S>( build_time: std::time::Duration, select_chain: Option, inherent_data_providers: inherents::InherentDataProviders, + can_author_with: CAW, ) where C: HeaderBackend + AuxStore + 'static, Algorithm: PowAlgorithm + Send + Sync + 'static, @@ -391,6 +392,7 @@ pub fn start_mine, C, Algorithm, E, SO, S>( E::Error: std::fmt::Debug, SO: SyncOracle + Send + Sync + 'static, S: SelectChain + 'static, + CAW: CanAuthorWith + Send + 'static, { if let Err(_) = register_pow_inherent_data_provider(&inherent_data_providers) { warn!("Registering inherent data provider for timestamp failed"); @@ -408,7 +410,8 @@ pub fn start_mine, C, Algorithm, E, SO, S>( &mut sync_oracle, build_time.clone(), select_chain.as_ref(), - &inherent_data_providers + &inherent_data_providers, + &can_author_with, ) { Ok(()) => (), Err(e) => error!( @@ -421,7 +424,7 @@ pub fn start_mine, C, Algorithm, E, SO, S>( }); } -fn mine_loop, C, Algorithm, E, SO, S>( +fn mine_loop, C, Algorithm, E, SO, S, CAW>( block_import: &mut BoxBlockImport, client: &C, algorithm: &Algorithm, @@ -432,6 +435,7 @@ fn mine_loop, C, Algorithm, E, SO, S>( build_time: std::time::Duration, select_chain: Option<&S>, inherent_data_providers: &inherents::InherentDataProviders, + can_author_with: &CAW, ) -> Result<(), Error> where C: HeaderBackend + AuxStore, Algorithm: PowAlgorithm, @@ -439,6 +443,7 @@ fn mine_loop, C, Algorithm, E, SO, S>( E::Error: std::fmt::Debug, SO: SyncOracle, S: SelectChain, + CAW: CanAuthorWith, { 'outer: loop { if sync_oracle.is_major_syncing() { @@ -462,6 +467,17 @@ fn mine_loop, C, Algorithm, E, SO, S>( (hash, header) }, }; + + if can_author_with.can_author_with(&BlockId::Hash(best_hash)) { + debug!( + target: "pow", + "Skipping proposal `can_author_with` returned `false`. \ + Probably a node update is required!" + ); + std::thread::sleep(std::time::Duration::new(1, 0)); + continue 'outer + } + let mut aux = PowAux::read(client, &best_hash)?; let mut proposer = env.init(&best_header) .map_err(|e| Error::Environment(format!("{:?}", e)))?; diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index 02f6c15b79492..13296507a61d5 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -31,7 +31,7 @@ use slots::Slots; pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND}; use codec::{Decode, Encode}; -use consensus_common::{BlockImport, Proposer, SyncOracle, SelectChain}; +use consensus_common::{BlockImport, Proposer, SyncOracle, SelectChain, CanAuthorWith}; use futures::{prelude::*, future::{self, Either}}; use futures_timer::Delay; use inherents::{InherentData, InherentDataProviders}; @@ -274,22 +274,24 @@ pub trait SlotCompatible { /// /// Every time a new slot is triggered, `worker.on_slot` is called and the future it returns is /// polled until completion, unless we are major syncing. -pub fn start_slot_worker( +pub fn start_slot_worker( slot_duration: SlotDuration, client: C, mut worker: W, mut sync_oracle: SO, inherent_data_providers: InherentDataProviders, timestamp_extractor: SC, + can_author_with: CAW, ) -> impl Future where B: BlockT, - C: SelectChain + Clone, + C: SelectChain, W: SlotWorker, W::OnSlot: Unpin, - SO: SyncOracle + Send + Clone, + SO: SyncOracle + Send, SC: SlotCompatible + Unpin, T: SlotData + Clone, + CAW: CanAuthorWith + Send, { let SlotDuration(slot_duration) = slot_duration; @@ -316,11 +318,23 @@ where } }; - Either::Left(worker.on_slot(chain_head, slot_info).map_err( - |e| { - warn!(target: "slots", "Encountered consensus error: {:?}", e); - }).or_else(|_| future::ready(Ok(()))) - ) + if can_author_with.can_author_with(&BlockId::Hash(chain_head.hash())) { + Either::Left( + worker.on_slot(chain_head, slot_info) + .map_err(|e| { + warn!(target: "slots", "Encountered consensus error: {:?}", e); + }) + .or_else(|_| future::ready(Ok(()))) + ) + } else { + warn!( + target: "slots", + "Unable to author block in slot {}. `can_author_with` returned `false`. \ + Probably a node update is required!", + slot_num, + ); + Either::Right(future::ready(Ok(()))) + } }).then(|res| { if let Err(err) = res { warn!(target: "slots", "Slots stream terminated with an error: {:?}", err); diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 0638a71d1c873..5037b490dd0f8 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -87,10 +87,7 @@ pub trait RuntimeInfo { fn native_version(&self) -> &NativeVersion; /// Extract RuntimeVersion of given :code block - fn runtime_version ( - &self, - ext: &mut E, - ) -> Option; + fn runtime_version (&self, ext: &mut E) -> error::Result; } #[cfg(test)] diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 23ea17a0c4eaa..f5d02f274aa88 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -25,7 +25,7 @@ use codec::{Decode, Encode}; use primitives::{NativeOrEncoded, traits::{CodeExecutor, Externalities}}; -use log::{trace, warn}; +use log::trace; use std::{result, cell::RefCell, panic::{UnwindSafe, AssertUnwindSafe}}; @@ -181,14 +181,8 @@ impl RuntimeInfo for NativeExecutor { fn runtime_version( &self, ext: &mut E, - ) -> Option { - match self.with_runtime(ext, |_runtime, version, _ext| Ok(Ok(version.clone()))) { - Ok(version) => Some(version), - Err(e) => { - warn!(target: "executor", "Failed to fetch runtime: {:?}", e); - None - } - } + ) -> Result { + self.with_runtime(ext, |_runtime, version, _ext| Ok(Ok(version.clone()))) } } diff --git a/client/rpc/src/state/state_light.rs b/client/rpc/src/state/state_light.rs index c3155ef4a93ba..4460f6f52bf99 100644 --- a/client/rpc/src/state/state_light.rs +++ b/client/rpc/src/state/state_light.rs @@ -41,7 +41,7 @@ use rpc::{ use api::Subscriptions; use client_api::backend::Backend; use client::{ - BlockchainEvents, Client, CallExecutor, + BlockchainEvents, Client, CallExecutor, error::Error as ClientError, light::{ blockchain::{future_header, RemoteBlockchain}, @@ -547,7 +547,8 @@ fn runtime_version>( Bytes(Vec::new()), ) .then(|version| ready(version.and_then(|version| - Decode::decode(&mut &version.0[..]).map_err(|_| client_err(ClientError::VersionInvalid)) + Decode::decode(&mut &version.0[..]) + .map_err(|e| client_err(ClientError::VersionInvalid(e.what().into()))) ))) } @@ -696,7 +697,7 @@ fn ignore_error(future: F) -> impl std::future::Future Ok(Some(result)), Err(()) => Ok(None), - })) + })) } #[cfg(test)] diff --git a/client/src/call_executor.rs b/client/src/call_executor.rs index e03293f2a722b..c3e9c0c12d4de 100644 --- a/client/src/call_executor.rs +++ b/client/src/call_executor.rs @@ -65,10 +65,10 @@ impl Clone for LocalCallExecutor where E: Clone { } impl CallExecutor for LocalCallExecutor -where - B: backend::Backend, - E: CodeExecutor + RuntimeInfo, - Block: BlockT, + where + B: backend::Backend, + E: CodeExecutor + RuntimeInfo, + Block: BlockT, { type Error = E::Error; @@ -195,7 +195,7 @@ where ); let version = self.executor.runtime_version(&mut ext); self.backend.destroy_state(state)?; - version.ok_or(error::Error::VersionInvalid.into()) + version.map_err(|e| error::Error::VersionInvalid(format!("{:?}", e))) } fn call_at_state< @@ -264,3 +264,21 @@ where Some(self.executor.native_version()) } } + +impl runtime_version::GetRuntimeVersion for LocalCallExecutor + where + B: backend::Backend, + E: CodeExecutor + RuntimeInfo, + Block: BlockT, +{ + fn native_version(&self) -> &runtime_version::NativeVersion { + self.executor.native_version() + } + + fn runtime_version( + &self, + at: &BlockId, + ) -> Result { + CallExecutor::runtime_version(self, at).map_err(|e| format!("{:?}", e)) + } +} diff --git a/client/src/client.rs b/client/src/client.rs index 578845a20930b..d8f3f9fead41e 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1747,9 +1747,10 @@ where } impl consensus::block_validation::Chain for Client - where BE: backend::Backend, - E: CallExecutor, - B: BlockT + where + BE: backend::Backend, + E: CallExecutor, + B: BlockT { fn block_status(&self, id: &BlockId) -> Result> { Client::block_status(self, id).map_err(|e| Box::new(e) as Box<_>) diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index 154ea7bb52f7c..3dc1cc7a392a7 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -31,7 +31,7 @@ use std::sync::Arc; use std::time::Duration; -use sr_primitives::traits::{Block as BlockT, DigestFor}; +use sr_primitives::{traits::{Block as BlockT, DigestFor}, generic::BlockId}; use futures::prelude::*; pub use inherents::InherentData; @@ -123,13 +123,59 @@ impl SyncOracle for NoNetwork { fn is_offline(&mut self) -> bool { false } } -impl SyncOracle for Arc -where T: ?Sized, for<'r> &'r T: SyncOracle -{ +impl SyncOracle for Arc where T: ?Sized, for<'r> &'r T: SyncOracle { fn is_major_syncing(&mut self) -> bool { <&T>::is_major_syncing(&mut &**self) } + fn is_offline(&mut self) -> bool { <&T>::is_offline(&mut &**self) } } + +/// Checks if the current active native block authoring implementation can author with the runtime +/// at the given block. +pub trait CanAuthorWith { + /// See trait docs for more information. + fn can_author_with(&self, at: &BlockId) -> bool; +} + +/// Checks if the node can author blocks by using +/// [`NativeVersion::can_author_with`](runtime_version::NativeVersion::can_author_with). +pub struct CanAuthorWithNativeVersion(T); + +impl CanAuthorWithNativeVersion { + /// Creates a new instance of `Self`. + pub fn new(inner: T) -> Self { + Self(inner) + } +} + +impl, Block: BlockT> CanAuthorWith + for CanAuthorWithNativeVersion +{ + fn can_author_with(&self, at: &BlockId) -> bool { + match self.0.runtime_version(at) { + Ok(version) => self.0.native_version().can_author_with(&version), + Err(e) => { + error!( + target: "CanAuthorWithNativeVersion", + "Failed to get runtime version at `{}` and will disable authoring. Error: {}", + at, + e, + ); + + false + } + } + } +} + +/// Returns always `true` for `can_author_with`. This is useful for tests. +pub struct AlwaysCanAuthor; + +impl CanAuthorWith for AlwaysCanAuthor { + fn can_author_with(&self, _: &BlockId) -> bool { + true + } +} diff --git a/primitives/sr-version/src/lib.rs b/primitives/sr-version/src/lib.rs index f342c25217926..d7d829bd4bb67 100644 --- a/primitives/sr-version/src/lib.rs +++ b/primitives/sr-version/src/lib.rs @@ -33,6 +33,9 @@ use codec::Decode; use sr_primitives::RuntimeString; pub use sr_primitives::create_runtime_str; +#[cfg(feature = "std")] +use sr_primitives::{traits::Block as BlockT, generic::BlockId}; + /// The identity of a particular API interface that the runtime might provide. pub type ApiId = [u8; 8]; @@ -165,6 +168,27 @@ impl NativeVersion { } } +/// Something that can provide the runtime version at a given block and the native runtime version. +#[cfg(feature = "std")] +pub trait GetRuntimeVersion { + /// Returns the version of the native runtime. + fn native_version(&self) -> &NativeVersion; + + /// Returns the version of runtime at the given block. + fn runtime_version(&self, at: &BlockId) -> Result; +} + +#[cfg(feature = "std")] +impl, Block: BlockT> GetRuntimeVersion for std::sync::Arc { + fn native_version(&self) -> &NativeVersion { + (&**self).native_version() + } + + fn runtime_version(&self, at: &BlockId) -> Result { + (&**self).runtime_version(at) + } +} + #[cfg(feature = "std")] mod apis_serialize { use super::*; From df51d3b565f05920d5c7767ecb0bafa22137cb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 28 Nov 2019 14:43:48 +0100 Subject: [PATCH 2/3] Update client/consensus/pow/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: André Silva --- client/consensus/pow/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 298a60fa593c5..9edec14ec14e5 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -472,7 +472,7 @@ fn mine_loop, C, Algorithm, E, SO, S, CAW>( "Skipping proposal `can_author_with` returned `false`. \ Probably a node update is required!" ); - std::thread::sleep(std::time::Duration::new(1, 0)); + std::thread::sleep(std::time::Duration::from_secs(1)); continue 'outer } From abc715ef9078c75d2e2e1d2a75b44c6358b198c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 28 Nov 2019 19:06:55 +0100 Subject: [PATCH 3/3] Fix compilation --- primitives/blockchain/src/error.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/primitives/blockchain/src/error.rs b/primitives/blockchain/src/error.rs index 5d25b6dec140e..6302edb3d2c60 100644 --- a/primitives/blockchain/src/error.rs +++ b/primitives/blockchain/src/error.rs @@ -28,7 +28,6 @@ use parity_scale_codec::Error as CodecError; /// Client Result type alias pub type Result = result::Result; - /// Error when the runtime failed to apply an extrinsic. #[derive(Debug, Display)] pub enum ApplyExtrinsicFailed { @@ -73,6 +72,7 @@ pub enum Error { InvalidAuthoritiesSet, /// Could not get runtime version. #[display(fmt = "Failed to get runtime version: {}", _0)] + #[from(ignore)] VersionInvalid(String), /// Genesis config is invalid. #[display(fmt = "Genesis config provided is invalid")] @@ -125,7 +125,6 @@ pub enum Error { InvalidStateRoot, /// A convenience variant for String #[display(fmt = "{}", _0)] - #[from(ignore)] Msg(String), } @@ -139,12 +138,6 @@ impl error::Error for Error { } } -impl From for Error { - fn from(s: String) -> Self { - Error::Msg(s) - } -} - impl<'a> From<&'a str> for Error { fn from(s: &'a str) -> Self { Error::Msg(s.into())