Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Grandpa validator set handoff justification #1190

Merged
merged 33 commits into from
Dec 8, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0fe90f7
core: make block justification optional
andresilva Nov 23, 2018
c9fdc56
runtime: update wasm binaries
andresilva Nov 23, 2018
cafa497
core: optionally pass justification on finalize_block
andresilva Nov 24, 2018
1fe5554
finality-grandpa: add channel to trigger authority set changes
andresilva Nov 26, 2018
8489541
finality-grandpa: move finalize_block to free function
andresilva Nov 27, 2018
208f097
finality-grandpa: add GrandpaOracle for auth set liveness checking
andresilva Nov 27, 2018
0c2912e
finality-grandpa: store justification on finalized transition blocks
andresilva Nov 28, 2018
b085e2e
finality-grandpa: check justification on authority set change blocks
andresilva Nov 28, 2018
fc284f7
finality-grandpa: poll grandpa liveness oracle every 10 seconds
andresilva Nov 29, 2018
4a90ba7
finality-grandpa: spawn grandpa oracle in service setup
andresilva Nov 30, 2018
0afa153
core: support multiple subscriptions per consensus gossip topic
andresilva Nov 30, 2018
4739e32
finality-grandpa: create and verify justifications
andresilva Nov 30, 2018
e4d5616
finality-grandpa: update to local branch of grandpa
andresilva Nov 30, 2018
f5ce910
finality-grandpa: update to finality-grandpa v0.5.0
andresilva Dec 2, 2018
711208b
finality-grandpa: move grandpa oracle code
andresilva Dec 2, 2018
cc46a45
finality-grandpa: fix canonality check
andresilva Dec 2, 2018
bac890e
finality-grandpa: clean up error handling
andresilva Dec 2, 2018
debd3b6
finality-grandpa: fix canonical_at_height
andresilva Dec 2, 2018
128eee0
finality-grandpa: fix tests
andresilva Dec 2, 2018
252817c
runtime: update wasm binaries
andresilva Dec 2, 2018
33392f5
core: add tests for finalizing block with justification
andresilva Dec 4, 2018
51eb2c5
finality-grandpa: improve validation of justifications
andresilva Dec 4, 2018
4a95dc3
core: remove unused IncompleteJustification block import error
andresilva Dec 4, 2018
101cba6
core: test multiple subscribers for same consensus gossip topic
andresilva Dec 4, 2018
a9e659b
Revert "finality-grandpa: improve validation of justifications"
andresilva Dec 5, 2018
d708849
finality-grandpa: fix commit validation
andresilva Dec 5, 2018
3ee13e0
Merge branch 'master' into andre/grandpa-handoff-justification
andresilva Dec 5, 2018
3f5732f
finality-grandpa: fix commit ancestry validation
andresilva Dec 5, 2018
1318a79
finality-grandpa: use grandpa v0.5.1
andresilva Dec 5, 2018
e5c593c
finality-grandpa: add docs
andresilva Dec 5, 2018
3a501ba
finality-grandpa: fix failing test
andresilva Dec 5, 2018
1e759d3
finality-grandpa: only allow a pending authority set change per fork
andresilva Dec 6, 2018
8c1981f
finality-grandpa: fix validator set transition test
andresilva Dec 7, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,6 @@ pub(crate) mod tests {
assert_eq!(
client.backend().blockchain().justification(BlockId::Hash(a2.hash())).unwrap(),
None,
)
);
}
}
2 changes: 1 addition & 1 deletion core/finality-grandpa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ substrate-finality-grandpa-primitives = { path = "primitives" }
rand = "0.6"

[dependencies.finality-grandpa]
version = "0.5.0"
version = "0.6.0"
features = ["derive-codec"]

[dev-dependencies]
Expand Down
5 changes: 5 additions & 0 deletions core/finality-grandpa/src/authorities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use parking_lot::RwLock;
use substrate_primitives::AuthorityId;

use std::cmp::Ord;
use std::collections::HashMap;
use std::fmt::Debug;
use std::ops::Add;
use std::sync::Arc;
Expand Down Expand Up @@ -63,6 +64,10 @@ where
pub(crate) fn set_id(&self) -> u64 {
self.inner.read().set_id
}

pub(crate) fn current_authorities(&self) -> HashMap<AuthorityId, u64> {
self.inner.read().current_authorities.iter().cloned().collect()
}
}

impl<H, N> From<AuthoritySet<H, N>> for SharedAuthoritySet<H, N> {
Expand Down
146 changes: 102 additions & 44 deletions core/finality-grandpa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,22 +311,17 @@ impl<B, E, Block: BlockT<Hash=H256>, RA> BlockStatus<Block> for Arc<Client<B, E,
}
}

/// The environment we run GRANDPA in.
struct Environment<B, E, Block: BlockT, N: Network, RA> {
// The chain interface exposed to GRANDPA.
#[derive(Clone)]
struct Chain<B, E, Block: BlockT, RA> {
inner: Arc<Client<B, E, Block, RA>>,
voters: Arc<HashMap<AuthorityId, u64>>,
config: Config,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
network: N,
set_id: u64,
}

impl<Block: BlockT<Hash=H256>, B, E, N, RA> grandpa::Chain<Block::Hash, NumberFor<Block>> for Environment<B, E, Block, N, RA> where
impl<Block: BlockT<Hash=H256>, B, E, RA> grandpa::Chain<Block::Hash, NumberFor<Block>> for Chain<B, E, Block, RA> where
Block: 'static,
B: Backend<Block, Blake2Hasher> + 'static,
E: CallExecutor<Block, Blake2Hasher> + 'static,
N: Network + 'static,
N::In: 'static,
NumberFor<Block>: BlockNumberOps,
{
fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> {
Expand Down Expand Up @@ -383,6 +378,32 @@ impl<Block: BlockT<Hash=H256>, B, E, N, RA> grandpa::Chain<Block::Hash, NumberFo
}
}

/// The environment we run GRANDPA in.
struct Environment<B, E, Block: BlockT, N: Network, RA> {
chain: Chain<B, E, Block, RA>,
voters: Arc<HashMap<AuthorityId, u64>>,
config: Config,
network: N,
set_id: u64,
}

impl<Block: BlockT<Hash=H256>, B, E, N, RA> grandpa::Chain<Block::Hash, NumberFor<Block>> for Environment<B, E, Block, N, RA> where
Block: 'static,
B: Backend<Block, Blake2Hasher> + 'static,
E: CallExecutor<Block, Blake2Hasher> + 'static,
N: Network + 'static,
N::In: 'static,
NumberFor<Block>: BlockNumberOps,
{
fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> {
self.chain.ancestry(base, block)
}

fn best_chain_containing(&self, block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> {
self.chain.best_chain_containing(block)
}
}

/// A new authority set along with the canonical block it changed at.
#[derive(Debug)]
struct NewAuthoritySet<H, N> {
Expand Down Expand Up @@ -482,8 +503,8 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
// schedule incoming messages from the network to be held until
// corresponding blocks are imported.
let incoming = UntilVoteTargetImported::new(
self.inner.import_notification_stream(),
self.inner.clone(),
self.chain.inner.import_notification_stream(),
self.chain.inner.clone(),
incoming,
);

Expand Down Expand Up @@ -512,7 +533,7 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
);

let encoded_state = (round, state).encode();
if let Err(e) = self.inner.backend()
if let Err(e) = self.chain.inner.backend()
.insert_aux(&[(LAST_COMPLETED_KEY, &encoded_state[..])], &[])
{
warn!(target: "afg", "Shutting down voter due to error bookkeeping last completed round in DB: {:?}", e);
Expand All @@ -523,7 +544,7 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
}

fn finalize_block(&self, hash: Block::Hash, number: NumberFor<Block>, round: u64, commit: Commit<Block>) -> Result<(), Self::Error> {
finalize_block(&*self.inner, &self.authority_set, hash, number, (round, commit).into())
finalize_block(&*self.chain.inner, &self.chain.authority_set, hash, number, (round, commit).into())
}

fn round_commit_timer(&self) -> Self::Timer {
Expand Down Expand Up @@ -559,7 +580,7 @@ impl<B, E, Block: BlockT<Hash=H256>, N, RA> voter::Environment<Block::Hash, Numb
struct GrandpaJustification<Block: BlockT> {
round: u64,
commit: Commit<Block>,
ancestry: Vec<Block::Header>,
votes_ancestries: Vec<Block::Header>,
}

impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
Expand All @@ -573,8 +594,8 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
RA: Send + Sync,
{
let mut ancestry_hashes = HashSet::new();
let mut ancestry = Vec::new();
let mut votes_ancestries_hashes = HashSet::new();
let mut votes_ancestries = Vec::new();

let error = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we do this lazily with a closure?

let msg = "invalid precommits for target commit".to_string();
Expand All @@ -593,8 +614,8 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
}

let parent_hash = current_header.parent_hash().clone();
if ancestry_hashes.insert(current_hash) {
ancestry.push(current_header);
if votes_ancestries_hashes.insert(current_hash) {
votes_ancestries.push(current_header);
}
current_hash = parent_hash;
},
Expand All @@ -603,14 +624,19 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
}
}

Ok(GrandpaJustification { round, commit, ancestry })
Ok(GrandpaJustification { round, commit, votes_ancestries })
}

// commit decoded as block justification must be verified and validated
fn decode_and_verify(
fn decode_and_verify<C>(
encoded: Vec<u8>,
set_id: u64,
) -> Result<GrandpaJustification<Block>, ClientError> {
voters: &HashMap<AuthorityId, u64>,
chain: &C,
) -> Result<GrandpaJustification<Block>, ClientError> where
NumberFor<Block>: grandpa::BlockNumberOps,
C: grandpa::Chain<Block::Hash, NumberFor<Block>>,
{
let justification = match GrandpaJustification::decode(&mut &*encoded) {
Some(justification) => justification,
_ => {
Expand All @@ -619,12 +645,26 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
}
};

let ancestry: HashMap<_, _> = justification.ancestry
match voter::validate_commit(
&justification.commit,
voters,
None,
chain,
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
) {
Ok(Some(_)) => {},
_ => {
let msg = "invalid commit in grandpa justification".to_string();
return Err(ClientErrorKind::BadJustification(msg).into());
}
}

let votes_ancestries: HashMap<_, _> = justification.votes_ancestries
.iter()
.cloned()
.map(|h: Block::Header| (h.hash(), h))
.collect();

let mut visited_hashes = HashSet::new();
for signed in justification.commit.precommits.iter() {
if let Err(_) = communication::check_message_sig::<Block>(
&grandpa::Message::Precommit(signed.precommit.clone()),
Expand All @@ -640,18 +680,24 @@ impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
let mut current_hash = signed.precommit.target_hash.clone();
loop {
if current_hash == justification.commit.target_hash { break; }
match ancestry.get(&current_hash) {
match votes_ancestries.get(&current_hash) {
Some(current_header) if *current_header.number() > justification.commit.target_number => {
current_hash = current_header.parent_hash().clone();
visited_hashes.insert(current_hash);
}
_ => {
return Err(ClientErrorKind::BadJustification(
"invalid ancestry proof in grandpa justification".to_string()).into());
"invalid precommit ancestry proof in grandpa justification".to_string()).into());
}
}
}
}

if visited_hashes != votes_ancestries.into_iter().map(|(k, _)| k).collect() {
return Err(ClientErrorKind::BadJustification(
"invalid precommit ancestries in grandpa justification with unused headers".to_string()).into());
}

Ok(justification)
rphmeier marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down Expand Up @@ -830,15 +876,15 @@ impl<Block: BlockT> SharedGrandpaOracle<Block> {
/// When using GRANDPA, the block import worker should be using this block import
/// object.
pub struct GrandpaBlockImport<B, E, Block: BlockT<Hash=H256>, RA, PRA> {
inner: Arc<Client<B, E, Block, RA>>,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
chain: Chain<B, E, Block, RA>,
authority_set_change: mpsc::UnboundedSender<NewAuthoritySet<Block::Hash, NumberFor<Block>>>,
authority_set_oracle: SharedGrandpaOracle<Block>,
api: Arc<PRA>,
}

impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> BlockImport<Block>
for GrandpaBlockImport<B, E, Block, RA, PRA> where
NumberFor<Block>: grandpa::BlockNumberOps,
B: Backend<Block, Blake2Hasher> + 'static,
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
DigestFor<Block>: Encode,
Expand All @@ -861,7 +907,7 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> BlockImport<Block>
let digest = block.header.digest().clone();
let is_live = self.authority_set_oracle.is_live();

let import_result = self.inner.import_block(block, new_authorities)?;
let import_result = self.chain.inner.import_block(block, new_authorities)?;
if import_result != ImportResult::Queued {
return Ok(import_result);
}
Expand All @@ -872,7 +918,7 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> BlockImport<Block>
)?;

if let Some(change) = maybe_change {
let mut authorities = self.authority_set.inner().write();
let mut authorities = self.chain.authority_set.inner().write();
authorities.add_pending_change(PendingChange {
next_authorities: change.next_authorities,
finalization_depth: change.delay,
Expand All @@ -881,11 +927,11 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> BlockImport<Block>
});

let encoded = authorities.encode();
self.inner.backend().insert_aux(&[(AUTHORITY_SET_KEY, &encoded[..])], &[])?;
self.chain.inner.backend().insert_aux(&[(AUTHORITY_SET_KEY, &encoded[..])], &[])?;
};

let enacts_change = self.authority_set.inner().read().enacts_change(number, |canon_number| {
canonical_at_height(&self.inner, (hash, number), canon_number)
let enacts_change = self.chain.authority_set.inner().read().enacts_change(number, |canon_number| {
canonical_at_height(&self.chain.inner, (hash, number), canon_number)
})?;

// a pending change is enacted by the given block, if the current
Expand All @@ -896,12 +942,14 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> BlockImport<Block>
if enacts_change && !is_live {
let justification = GrandpaJustification::decode_and_verify(
justification,
self.authority_set.set_id(),
self.chain.authority_set.set_id(),
&self.chain.authority_set.current_authorities(),
&self.chain,
)?;

let result = finalize_block(
&*self.inner,
&self.authority_set,
&*self.chain.inner,
&self.chain.authority_set,
hash,
number,
justification.into(),
Expand Down Expand Up @@ -987,7 +1035,7 @@ where

type Error = <Client<B, E, Block, RA> as Authorities<Block>>::Error;
fn authorities(&self, at: &BlockId<Block>) -> Result<Vec<AuthorityId>, Self::Error> {
self.inner.authorities_at(at)
self.chain.inner.authorities_at(at)
}
}

Expand Down Expand Up @@ -1042,10 +1090,14 @@ pub fn block_import<B, E, Block: BlockT<Hash=H256>, RA, PRA>(

let authority_set_oracle = SharedGrandpaOracle::empty();

let chain = Chain {
inner: client.clone(),
authority_set: authority_set.clone(),
};

Ok((
GrandpaBlockImport {
inner: client.clone(),
authority_set: authority_set.clone(),
chain,
authority_set_change: authority_set_change_tx,
authority_set_oracle: authority_set_oracle.clone(),
api
Expand Down Expand Up @@ -1152,17 +1204,19 @@ pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA>(
))?
};

let voters = authority_set.inner().read().current().1.iter()
.cloned()
.collect();
let voters = authority_set.current_authorities();

let initial_environment = Arc::new(Environment {
let chain = Chain {
inner: client.clone(),
authority_set: authority_set.clone(),
};

let initial_environment = Arc::new(Environment {
chain,
config: config.clone(),
voters: Arc::new(voters),
network: network.clone(),
set_id: authority_set.set_id(),
authority_set: authority_set.clone(),
});

let initial_state = (initial_environment, last_round_number, last_state, authority_set_change.into_future());
Expand Down Expand Up @@ -1211,13 +1265,17 @@ pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA>(
let authority_set = authority_set.clone();

let trigger_authority_set_change = |new: NewAuthoritySet<_, _>, authority_set_change| {
let env = Arc::new(Environment {
let chain = Chain {
inner: client,
authority_set,
};

let env = Arc::new(Environment {
chain,
config,
voters: Arc::new(new.authorities.into_iter().collect()),
set_id: new.set_id,
network,
authority_set,
});

// start the new authority set using the block where the
Expand Down