diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 64949da009e243c3bf7ad92956793308e92fae05..5bbe6f6f00296ff44ae1d6c06d6f301dc2c7e86b 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1589,9 +1589,9 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd795898c348a8ec9edc66ec9e014031c764d4c88cc26d09b492cd93eb41339" +checksum = "c6447e2f8178843749e8c8003206def83ec124a7859475395777a28b5338647c" dependencies = [ "either", "futures 0.3.12", @@ -7089,6 +7089,7 @@ version = "0.9.0" dependencies = [ "assert_matches", "derive_more", + "dyn-clone", "finality-grandpa", "fork-tree", "futures 0.3.12", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 58c98e529c3196afed59536ecb69f6c18a99f977..53cc0545e9d863ce8fa72ddda568cf4f438ed3c4 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. spec_version: 264, - impl_version: 0, + impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, }; diff --git a/substrate/client/finality-grandpa/Cargo.toml b/substrate/client/finality-grandpa/Cargo.toml index d1ee2fe6b4522e467f52b018adda2d1b6c72a8c5..7ae5666c7bc8474a98fa28711e5c7e7377874c0e 100644 --- a/substrate/client/finality-grandpa/Cargo.toml +++ b/substrate/client/finality-grandpa/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] derive_more = "0.99.2" +dyn-clone = "1.0" fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } futures = "0.3.9" futures-timer = "3.0.1" @@ -43,13 +44,13 @@ sc-network-gossip = { version = "0.9.0", path = "../network-gossip" } sp-finality-grandpa = { version = "3.0.0", path = "../../primitives/finality-grandpa" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.9.0"} sc-block-builder = { version = "0.9.0", path = "../block-builder" } -finality-grandpa = { version = "0.13.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.14.0", features = ["derive-codec"] } pin-project = "1.0.4" linked-hash-map = "0.5.2" [dev-dependencies] assert_matches = "1.3.0" -finality-grandpa = { version = "0.13.0", features = ["derive-codec", "test-helpers"] } +finality-grandpa = { version = "0.14.0", features = ["derive-codec", "test-helpers"] } sc-network = { version = "0.9.0", path = "../network" } sc-network-test = { version = "0.8.0", path = "../network/test" } sp-keyring = { version = "3.0.0", path = "../../primitives/keyring" } diff --git a/substrate/client/finality-grandpa/rpc/Cargo.toml b/substrate/client/finality-grandpa/rpc/Cargo.toml index 58aa78a38b103f20a8f1d12503616b26b6b1ed0f..ff5b4cafdae77c3f12f6f9090ffdc204f3089acf 100644 --- a/substrate/client/finality-grandpa/rpc/Cargo.toml +++ b/substrate/client/finality-grandpa/rpc/Cargo.toml @@ -14,7 +14,7 @@ sc-rpc = { version = "3.0.0", path = "../../rpc" } sp-blockchain = { version = "3.0.0", path = "../../../primitives/blockchain" } sp-core = { version = "3.0.0", path = "../../../primitives/core" } sp-runtime = { version = "3.0.0", path = "../../../primitives/runtime" } -finality-grandpa = { version = "0.13.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.14.0", features = ["derive-codec"] } jsonrpc-core = "15.1.0" jsonrpc-core-client = "15.1.0" jsonrpc-derive = "15.1.0" diff --git a/substrate/client/finality-grandpa/src/environment.rs b/substrate/client/finality-grandpa/src/environment.rs index 55a60e16dfd31a4a14f95efd0dd0fed18456f653..7925a674c2983826ec70073830e8bd72f01ea3ca 100644 --- a/substrate/client/finality-grandpa/src/environment.rs +++ b/substrate/client/finality-grandpa/src/environment.rs @@ -592,100 +592,6 @@ where fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> { ancestry(&self.client, base, block) } - - fn best_chain_containing(&self, block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> { - // NOTE: when we finalize an authority set change through the sync protocol the voter is - // signaled asynchronously. therefore the voter could still vote in the next round - // before activating the new set. the `authority_set` is updated immediately thus we - // restrict the voter based on that. - if self.set_id != self.authority_set.set_id() { - return None; - } - - let base_header = match self.client.header(BlockId::Hash(block)).ok()? { - Some(h) => h, - None => { - debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find base block", block); - return None; - } - }; - - // we refuse to vote beyond the current limit number where transitions are scheduled to - // occur. - // once blocks are finalized that make that transition irrelevant or activate it, - // we will proceed onwards. most of the time there will be no pending transition. - // the limit, if any, is guaranteed to be higher than or equal to the given base number. - let limit = self.authority_set.current_limit(*base_header.number()); - debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit); - - match self.select_chain.finality_target(block, None) { - Ok(Some(best_hash)) => { - let best_header = self.client.header(BlockId::Hash(best_hash)).ok()? - .expect("Header known to exist after `finality_target` call; qed"); - - // check if our vote is currently being limited due to a pending change - let limit = limit.filter(|limit| limit < best_header.number()); - let target; - - let target_header = if let Some(target_number) = limit { - let mut target_header = best_header.clone(); - - // walk backwards until we find the target block - loop { - if *target_header.number() < target_number { - unreachable!( - "we are traversing backwards from a known block; \ - blocks are stored contiguously; \ - qed" - ); - } - - if *target_header.number() == target_number { - break; - } - - target_header = self.client.header(BlockId::Hash(*target_header.parent_hash())).ok()? - .expect("Header known to exist after `finality_target` call; qed"); - } - - target = target_header; - &target - } else { - // otherwise just use the given best as the target - &best_header - }; - - // restrict vote according to the given voting rule, if the - // voting rule doesn't restrict the vote then we keep the - // previous target. - // - // note that we pass the original `best_header`, i.e. before the - // authority set limit filter, which can be considered a - // mandatory/implicit voting rule. - // - // we also make sure that the restricted vote is higher than the - // round base (i.e. last finalized), otherwise the value - // returned by the given voting rule is ignored and the original - // target is used instead. - self.voting_rule - .restrict_vote(&*self.client, &base_header, &best_header, target_header) - .filter(|(_, restricted_number)| { - // we can only restrict votes within the interval [base, target] - restricted_number >= base_header.number() && - restricted_number < target_header.number() - }) - .or_else(|| Some((target_header.hash(), *target_header.number()))) - }, - Ok(None) => { - debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find target block", block); - None - } - Err(e) => { - debug!(target: "afg", "Encountered error finding best chain containing {:?}: {:?}", block, e); - None - } - } - } } @@ -733,6 +639,14 @@ where NumberFor<Block>: BlockNumberOps, { type Timer = Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + Sync>>; + type BestChain = Pin< + Box< + dyn Future<Output = Result<Option<(Block::Hash, NumberFor<Block>)>, Self::Error>> + + Send + + Sync + >, + >; + type Id = AuthorityId; type Signature = AuthoritySignature; @@ -747,6 +661,119 @@ where type Error = CommandOrError<Block::Hash, NumberFor<Block>>; + fn best_chain_containing(&self, block: Block::Hash) -> Self::BestChain { + let find_best_chain = || { + // NOTE: when we finalize an authority set change through the sync protocol the voter is + // signaled asynchronously. therefore the voter could still vote in the next round + // before activating the new set. the `authority_set` is updated immediately thus we + // restrict the voter based on that. + if self.set_id != self.authority_set.set_id() { + return None; + } + + let base_header = match self.client.header(BlockId::Hash(block)).ok()? { + Some(h) => h, + None => { + debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find base block", block); + return None; + } + }; + + // we refuse to vote beyond the current limit number where transitions are scheduled to + // occur. + // once blocks are finalized that make that transition irrelevant or activate it, + // we will proceed onwards. most of the time there will be no pending transition. + // the limit, if any, is guaranteed to be higher than or equal to the given base number. + let limit = self.authority_set.current_limit(*base_header.number()); + debug!(target: "afg", "Finding best chain containing block {:?} with number limit {:?}", block, limit); + + match self.select_chain.finality_target(block, None) { + Ok(Some(best_hash)) => { + let best_header = self + .client + .header(BlockId::Hash(best_hash)) + .ok()? + .expect("Header known to exist after `finality_target` call; qed"); + + // check if our vote is currently being limited due to a pending change + let limit = limit.filter(|limit| limit < best_header.number()); + + if let Some(target_number) = limit { + let mut target_header = best_header.clone(); + + // walk backwards until we find the target block + loop { + if *target_header.number() < target_number { + unreachable!( + "we are traversing backwards from a known block; \ + blocks are stored contiguously; \ + qed" + ); + } + + if *target_header.number() == target_number { + break; + } + + target_header = self + .client + .header(BlockId::Hash(*target_header.parent_hash())) + .ok()? + .expect("Header known to exist after `finality_target` call; qed"); + } + + Some((base_header, best_header, target_header)) + } else { + // otherwise just use the given best as the target + Some((base_header, best_header.clone(), best_header)) + } + } + Ok(None) => { + debug!(target: "afg", "Encountered error finding best chain containing {:?}: couldn't find target block", block); + None + } + Err(e) => { + debug!(target: "afg", "Encountered error finding best chain containing {:?}: {:?}", block, e); + None + } + } + }; + + if let Some((base_header, best_header, target_header)) = find_best_chain() { + // restrict vote according to the given voting rule, if the + // voting rule doesn't restrict the vote then we keep the + // previous target. + // + // note that we pass the original `best_header`, i.e. before the + // authority set limit filter, which can be considered a + // mandatory/implicit voting rule. + // + // we also make sure that the restricted vote is higher than the + // round base (i.e. last finalized), otherwise the value + // returned by the given voting rule is ignored and the original + // target is used instead. + let rule_fut = self.voting_rule.restrict_vote( + self.client.clone(), + &base_header, + &best_header, + &target_header, + ); + + Box::pin(async move { + Ok(rule_fut + .await + .filter(|(_, restricted_number)| { + // we can only restrict votes within the interval [base, target] + restricted_number >= base_header.number() + && restricted_number < target_header.number() + }) + .or_else(|| Some((target_header.hash(), *target_header.number())))) + }) + } else { + Box::pin(future::ok(None)) + } + } + fn round_data( &self, round: RoundNumber, diff --git a/substrate/client/finality-grandpa/src/justification.rs b/substrate/client/finality-grandpa/src/justification.rs index 9429acff06d8ce9f160bc78de66bf16032a79918..eba909bad5ef687d5091009d324dbc7f906e7d40 100644 --- a/substrate/client/finality-grandpa/src/justification.rs +++ b/substrate/client/finality-grandpa/src/justification.rs @@ -217,8 +217,4 @@ impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for A Ok(route) } - - fn best_chain_containing(&self, _block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> { - None - } } diff --git a/substrate/client/finality-grandpa/src/lib.rs b/substrate/client/finality-grandpa/src/lib.rs index 75500a894d745293380d87aa7f07e00ef0795638..809e14e5c90b5e801ff41ad52f0c7538d659541b 100644 --- a/substrate/client/finality-grandpa/src/lib.rs +++ b/substrate/client/finality-grandpa/src/lib.rs @@ -127,7 +127,8 @@ pub use notification::{GrandpaJustificationSender, GrandpaJustificationStream}; pub use import::GrandpaBlockImport; pub use justification::GrandpaJustification; pub use voting_rule::{ - BeforeBestBlockBy, ThreeQuartersOfTheUnfinalizedChain, VotingRule, VotingRulesBuilder + BeforeBestBlockBy, ThreeQuartersOfTheUnfinalizedChain, VotingRule, VotingRuleResult, + VotingRulesBuilder, }; pub use finality_grandpa::voter::report; pub use finality_proof::{prove_warp_sync, WarpSyncFragmentCache}; diff --git a/substrate/client/finality-grandpa/src/observer.rs b/substrate/client/finality-grandpa/src/observer.rs index c9db917e1699a89f11c816e6b8edbc7a4f54fc47..3054a9df61c56e23d9bf4aa70ba04e4be191686f 100644 --- a/substrate/client/finality-grandpa/src/observer.rs +++ b/substrate/client/finality-grandpa/src/observer.rs @@ -57,11 +57,6 @@ impl<'a, Block, Client> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> { environment::ancestry(&self.client, base, block) } - - fn best_chain_containing(&self, _block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> { - // only used by voter - None - } } fn grandpa_observer<BE, Block: BlockT, Client, S, F>( diff --git a/substrate/client/finality-grandpa/src/tests.rs b/substrate/client/finality-grandpa/src/tests.rs index 4918255d027a8f4913fc8f1a633a54600618d4c3..921b49db61c2558bd80f49bda6851d7e0e56f959 100644 --- a/substrate/client/finality-grandpa/src/tests.rs +++ b/substrate/client/finality-grandpa/src/tests.rs @@ -1355,7 +1355,7 @@ where #[test] fn grandpa_environment_respects_voting_rules() { - use finality_grandpa::Chain; + use finality_grandpa::voter::Environment; let peers = &[Ed25519Keyring::Alice]; let voters = make_ids(peers); @@ -1390,25 +1390,25 @@ fn grandpa_environment_respects_voting_rules() { // the unrestricted environment should just return the best block assert_eq!( - unrestricted_env.best_chain_containing( + futures::executor::block_on(unrestricted_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 21, ); // both the other environments should return block 16, which is 3/4 of the // way in the unfinalized chain assert_eq!( - three_quarters_env.best_chain_containing( + futures::executor::block_on(three_quarters_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 16, ); assert_eq!( - default_env.best_chain_containing( + futures::executor::block_on(default_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 16, ); @@ -1417,18 +1417,18 @@ fn grandpa_environment_respects_voting_rules() { // the 3/4 environment should propose block 21 for voting assert_eq!( - three_quarters_env.best_chain_containing( + futures::executor::block_on(three_quarters_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 21, ); // while the default environment will always still make sure we don't vote // on the best block (2 behind) assert_eq!( - default_env.best_chain_containing( + futures::executor::block_on(default_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 19, ); @@ -1439,9 +1439,9 @@ fn grandpa_environment_respects_voting_rules() { // best block, there's a hard rule that we can't cast any votes lower than // the given base (#21). assert_eq!( - default_env.best_chain_containing( + futures::executor::block_on(default_env.best_chain_containing( peer.client().info().finalized_hash - ).unwrap().1, + )).unwrap().unwrap().1, 21, ); } diff --git a/substrate/client/finality-grandpa/src/voting_rule.rs b/substrate/client/finality-grandpa/src/voting_rule.rs index a861e792755feaed872caff1cc644ceab751e8b8..e7b74c3e3296e876a3a68a180d1382447895b13b 100644 --- a/substrate/client/finality-grandpa/src/voting_rule.rs +++ b/substrate/client/finality-grandpa/src/voting_rule.rs @@ -22,14 +22,22 @@ //! restrictions that are taken into account by the GRANDPA environment when //! selecting a finality target to vote on. +use std::future::Future; use std::sync::Arc; +use std::pin::Pin; + +use dyn_clone::DynClone; use sc_client_api::blockchain::HeaderBackend; use sp_runtime::generic::BlockId; use sp_runtime::traits::{Block as BlockT, Header, NumberFor, One, Zero}; +/// A future returned by a `VotingRule` to restrict a given vote, if any restriction is necessary. +pub type VotingRuleResult<Block> = + Pin<Box<dyn Future<Output = Option<(<Block as BlockT>::Hash, NumberFor<Block>)>> + Send + Sync>>; + /// A trait for custom voting rules in GRANDPA. -pub trait VotingRule<Block, B>: Send + Sync where +pub trait VotingRule<Block, B>: DynClone + Send + Sync where Block: BlockT, B: HeaderBackend<Block>, { @@ -47,11 +55,11 @@ pub trait VotingRule<Block, B>: Send + Sync where /// execution of voting rules wherein `current_target <= best_target`. fn restrict_vote( &self, - backend: &B, + backend: Arc<B>, base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)>; + ) -> VotingRuleResult<Block>; } impl<Block, B> VotingRule<Block, B> for () where @@ -60,12 +68,12 @@ impl<Block, B> VotingRule<Block, B> for () where { fn restrict_vote( &self, - _backend: &B, + _backend: Arc<B>, _base: &Block::Header, _best_target: &Block::Header, _current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)> { - None + ) -> VotingRuleResult<Block> { + Box::pin(async { None }) } } @@ -80,15 +88,15 @@ impl<Block, B> VotingRule<Block, B> for BeforeBestBlockBy<NumberFor<Block>> wher { fn restrict_vote( &self, - backend: &B, + backend: Arc<B>, _base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)> { + ) -> VotingRuleResult<Block> { use sp_arithmetic::traits::Saturating; if current_target.number().is_zero() { - return None; + return Box::pin(async { None }); } // find the target number restricted by this rule @@ -96,21 +104,24 @@ impl<Block, B> VotingRule<Block, B> for BeforeBestBlockBy<NumberFor<Block>> wher // our current target is already lower than this rule would restrict if target_number >= *current_target.number() { - return None; + return Box::pin(async { None }); } + let current_target = current_target.clone(); + // find the block at the given target height - find_target( - backend, - target_number, - current_target, - ) + Box::pin(std::future::ready(find_target( + &*backend, + target_number.clone(), + ¤t_target, + ))) } } /// A custom voting rule that limits votes towards 3/4 of the unfinalized chain, /// using the given `base` and `best_target` to figure where the 3/4 target /// should fall. +#[derive(Clone)] pub struct ThreeQuartersOfTheUnfinalizedChain; impl<Block, B> VotingRule<Block, B> for ThreeQuartersOfTheUnfinalizedChain where @@ -119,11 +130,11 @@ impl<Block, B> VotingRule<Block, B> for ThreeQuartersOfTheUnfinalizedChain where { fn restrict_vote( &self, - backend: &B, + backend: Arc<B>, base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)> { + ) -> VotingRuleResult<Block> { // target a vote towards 3/4 of the unfinalized chain (rounding up) let target_number = { let two = NumberFor::<Block>::one() + One::one(); @@ -138,15 +149,15 @@ impl<Block, B> VotingRule<Block, B> for ThreeQuartersOfTheUnfinalizedChain where // our current target is already lower than this rule would restrict if target_number >= *current_target.number() { - return None; + return Box::pin(async { None }); } // find the block at the given target height - find_target( - backend, + Box::pin(std::future::ready(find_target( + &*backend, target_number, current_target, - ) + ))) } } @@ -195,37 +206,42 @@ impl<B, Block> Clone for VotingRules<B, Block> { impl<Block, B> VotingRule<Block, B> for VotingRules<Block, B> where Block: BlockT, - B: HeaderBackend<Block>, + B: HeaderBackend<Block> + 'static, { fn restrict_vote( &self, - backend: &B, + backend: Arc<B>, base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)> { - let restricted_target = self.rules.iter().fold( - current_target.clone(), - |current_target, rule| { - rule.restrict_vote( - backend, - base, - best_target, - ¤t_target, - ) + ) -> VotingRuleResult<Block> { + let rules = self.rules.clone(); + let base = base.clone(); + let best_target = best_target.clone(); + let current_target = current_target.clone(); + + Box::pin(async move { + let mut restricted_target = current_target.clone(); + + for rule in rules.iter() { + if let Some(header) = rule + .restrict_vote(backend.clone(), &base, &best_target, &restricted_target) + .await .and_then(|(hash, _)| backend.header(BlockId::Hash(hash)).ok()) .and_then(std::convert::identity) - .unwrap_or(current_target) - }, - ); - - let restricted_hash = restricted_target.hash(); - - if restricted_hash != current_target.hash() { - Some((restricted_hash, *restricted_target.number())) - } else { - None - } + { + restricted_target = header; + } + } + + let restricted_hash = restricted_target.hash(); + + if restricted_hash != current_target.hash() { + Some((restricted_hash, *restricted_target.number())) + } else { + None + } + }) } } @@ -237,7 +253,7 @@ pub struct VotingRulesBuilder<Block, B> { impl<Block, B> Default for VotingRulesBuilder<Block, B> where Block: BlockT, - B: HeaderBackend<Block>, + B: HeaderBackend<Block> + 'static, { fn default() -> Self { VotingRulesBuilder::new() @@ -248,7 +264,7 @@ impl<Block, B> Default for VotingRulesBuilder<Block, B> where impl<Block, B> VotingRulesBuilder<Block, B> where Block: BlockT, - B: HeaderBackend<Block>, + B: HeaderBackend<Block> + 'static, { /// Return a new voting rule builder using the given backend. pub fn new() -> Self { @@ -285,14 +301,15 @@ impl<Block, B> VotingRulesBuilder<Block, B> where impl<Block, B> VotingRule<Block, B> for Box<dyn VotingRule<Block, B>> where Block: BlockT, B: HeaderBackend<Block>, + Self: Clone, { fn restrict_vote( &self, - backend: &B, + backend: Arc<B>, base: &Block::Header, best_target: &Block::Header, current_target: &Block::Header, - ) -> Option<(Block::Hash, NumberFor<Block>)> { + ) -> VotingRuleResult<Block> { (**self).restrict_vote(backend, base, best_target, current_target) } } diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index a9ba0ccc56f3b655ea5197039aa3ca3225e62de0..3e85ff50d3e12e600bbdeafa3f9d5829e1ee9a17 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -30,7 +30,7 @@ pallet-session = { version = "3.0.0", default-features = false, path = "../sessi [dev-dependencies] frame-benchmarking = { version = "3.0.0", path = "../benchmarking" } -grandpa = { package = "finality-grandpa", version = "0.13.0", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.14.0", features = ["derive-codec"] } sp-io = { version = "3.0.0", path = "../../primitives/io" } sp-keyring = { version = "3.0.0", path = "../../primitives/keyring" } pallet-balances = { version = "3.0.0", path = "../balances" } diff --git a/substrate/primitives/finality-grandpa/Cargo.toml b/substrate/primitives/finality-grandpa/Cargo.toml index c8ff2fc0a2e651c8075bec0e7360eb5b5e73b78e..95aa65c930f7897d60cb1bc030542918870cd49c 100644 --- a/substrate/primitives/finality-grandpa/Cargo.toml +++ b/substrate/primitives/finality-grandpa/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.13.0", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.14.0", default-features = false, features = ["derive-codec"] } log = { version = "0.4.8", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } sp-api = { version = "3.0.0", default-features = false, path = "../api" }