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 all commits
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
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 48 additions & 3 deletions core/client/db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,14 +797,24 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
Ok(())
}

fn finalize_block(&self, block: BlockId<Block>) -> Result<(), client::error::Error> {
fn finalize_block(&self, block: BlockId<Block>, justification: Option<Justification>)
-> Result<(), client::error::Error>
{
use runtime_primitives::traits::Header;

if let Some(header) = ::client::blockchain::HeaderBackend::header(&self.blockchain, block)? {
let mut transaction = DBTransaction::new();
// TODO: ensure best chain contains this block.
let hash = header.hash();
self.note_finalized(&mut transaction, &header, hash.clone())?;
if let Some(justification) = justification {
let number = header.number().clone();
transaction.put(
columns::JUSTIFICATION,
&::utils::number_and_hash_to_lookup_key(number, hash.clone()),
&justification.encode(),
);
}
self.storage.db.write(transaction).map_err(db_err)?;
self.blockchain.update_meta(hash, header.number().clone(), false, true);
Ok(())
Expand Down Expand Up @@ -1196,8 +1206,8 @@ mod tests {
assert!(backend.storage.db.get(::columns::STATE, key.as_bytes()).unwrap().is_none());
}

backend.finalize_block(BlockId::Number(1)).unwrap();
backend.finalize_block(BlockId::Number(2)).unwrap();
backend.finalize_block(BlockId::Number(1), None).unwrap();
backend.finalize_block(BlockId::Number(2), None).unwrap();
assert!(backend.storage.db.get(::columns::STATE, key.as_bytes()).unwrap().is_none());
}

Expand Down Expand Up @@ -1499,4 +1509,39 @@ mod tests {
backend.insert_aux(&[], &[&b"test"[..]]).unwrap();
assert!(backend.get_aux(b"test").unwrap().is_none());
}

#[test]
fn test_finalize_block_with_justification() {
use client::blockchain::{Backend as BlockChainBackend};

let backend = Backend::<Block>::new_test(0, 0);

{
let mut op = backend.begin_operation(BlockId::Hash(Default::default())).unwrap();
let header = Header {
number: 0,
parent_hash: Default::default(),
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};

op.set_block_data(
header,
Some(vec![]),
None,
NewBlockState::Best,
).unwrap();

backend.commit_operation(op).unwrap();
}

let justification = Some(vec![1, 2, 3]);
backend.finalize_block(BlockId::Number(0), justification.clone()).unwrap();

assert_eq!(
backend.blockchain().justification(BlockId::Number(0)).unwrap(),
justification,
);
}
}
2 changes: 1 addition & 1 deletion core/client/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub trait Backend<Block, H>: Send + Sync where
fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
/// Finalize block with given Id. This should only be called if the parent of the given
/// block has been finalized.
fn finalize_block(&self, block: BlockId<Block>) -> error::Result<()>;
fn finalize_block(&self, block: BlockId<Block>, justification: Option<Justification>) -> error::Result<()>;
/// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain;
/// Returns reference to changes trie storage.
Expand Down
111 changes: 84 additions & 27 deletions core/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
origin: BlockOrigin,
hash: Block::Hash,
import_headers: PrePostHeader<Block::Header>,
justification: Justification,
justification: Option<Justification>,
body: Option<Vec<Block::Extrinsic>>,
authorities: Option<Vec<AuthorityId>>,
finalized: bool,
Expand Down Expand Up @@ -624,7 +624,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
// ensure parent block is finalized to maintain invariant that
// finality is called sequentially.
if finalized {
self.apply_finality(parent_hash, last_best, make_notifications)?;
self.apply_finality(parent_hash, None, last_best, make_notifications)?;
}

let tags = self.transaction_tags(parent_hash, &body)?;
Expand Down Expand Up @@ -678,7 +678,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
transaction.set_block_data(
import_headers.post().clone(),
body,
Some(justification),
justification,
leaf_state,
)?;

Expand Down Expand Up @@ -727,8 +727,16 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
Ok(ImportResult::Queued)
}

/// Finalizes all blocks up to given.
fn apply_finality(&self, block: Block::Hash, best_block: Block::Hash, notify: bool) -> error::Result<()> {
/// Finalizes all blocks up to given. If a justification is provided it is
/// stored with the given finalized block (any other finalized blocks are
/// left unjustified).
fn apply_finality(
&self,
block: Block::Hash,
justification: Option<Justification>,
best_block: Block::Hash,
notify: bool,
) -> error::Result<()> {
// find tree route from last finalized to given block.
let last_finalized = self.backend.blockchain().last_finalized()?;

Expand Down Expand Up @@ -759,10 +767,15 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
// `block`.
}

for finalize_new in route_from_finalized.enacted() {
self.backend.finalize_block(BlockId::Hash(finalize_new.hash))?;
let enacted = route_from_finalized.enacted();
assert!(enacted.len() > 0);
for finalize_new in &enacted[..enacted.len() - 1] {
self.backend.finalize_block(BlockId::Hash(finalize_new.hash), None)?;
}

assert_eq!(enacted.last().map(|e| e.hash), Some(block));
self.backend.finalize_block(BlockId::Hash(block), justification)?;

if notify {
// sometimes when syncing, tons of blocks can be finalized at once.
// we'll send notifications spuriously in that case.
Expand Down Expand Up @@ -791,15 +804,15 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
/// Pass a flag to indicate whether finality notifications should be propagated.
/// This is usually tied to some synchronization state, where we don't send notifications
/// while performing major synchronization work.
pub fn finalize_block(&self, id: BlockId<Block>, notify: bool) -> error::Result<()> {
pub fn finalize_block(&self, id: BlockId<Block>, justification: Option<Justification>, notify: bool) -> error::Result<()> {
let last_best = self.backend.blockchain().info()?.best_hash;
let to_finalize_hash = match id {
BlockId::Hash(h) => h,
BlockId::Number(n) => self.backend.blockchain().hash(n)?
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("No block with number {:?}", n)))?,
};

self.apply_finality(to_finalize_hash, last_best, notify)
self.apply_finality(to_finalize_hash, justification, last_best, notify)
}

/// Attempts to revert the chain by `n` blocks. Returns the number of blocks that were
Expand Down Expand Up @@ -858,7 +871,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
-> error::Result<Option<SignedBlock<Block>>>
{
Ok(match (self.header(id)?, self.body(id)?, self.justification(id)?) {
(Some(header), Some(extrinsics), Some(justification)) =>
(Some(header), Some(extrinsics), justification) =>
Some(SignedBlock { block: Block::new(header, extrinsics), justification }),
_ => None,
})
Expand Down Expand Up @@ -1064,7 +1077,8 @@ impl<B, E, Block, RA> consensus::BlockImport<Block> for Client<B, E, Block, RA>
{
type Error = Error;

/// Import a checked and validated block
/// Import a checked and validated block. If a justification is provided in
/// `ImportBlock` then `finalized` *must* be true.
fn import_block(
&self,
import_block: ImportBlock<Block>,
Expand All @@ -1081,6 +1095,9 @@ impl<B, E, Block, RA> consensus::BlockImport<Block> for Client<B, E, Block, RA>
finalized,
auxiliary,
} = import_block;

assert!(justification.is_some() && finalized || justification.is_none());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should create a type: enum Finality { Finalized, FinalizedAndJustified(justification), Unfinalized }.


let parent_hash = header.parent_hash().clone();

match self.backend.blockchain().status(BlockId::Hash(parent_hash))? {
Expand Down Expand Up @@ -1254,7 +1271,7 @@ pub(crate) mod tests {
nonce: *nonces.entry(from).and_modify(|n| { *n = *n + 1 }).or_default(),
}).unwrap();
}
remote_client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
remote_client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();

let header = remote_client.header(&BlockId::Number(i as u64 + 1)).unwrap().unwrap();
let trie_root = header.digest().log(DigestItem::as_changes_trie_root)
Expand Down Expand Up @@ -1334,7 +1351,7 @@ pub(crate) mod tests {

let builder = client.new_block().unwrap();

client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();

assert_eq!(client.info().unwrap().chain.best_number, 1);
}
Expand All @@ -1352,7 +1369,7 @@ pub(crate) mod tests {
nonce: 0,
}).unwrap();

client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();

assert_eq!(client.info().unwrap().chain.best_number, 1);
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
Expand Down Expand Up @@ -1404,7 +1421,7 @@ pub(crate) mod tests {
nonce: 0,
}).is_err());

client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();

assert_eq!(client.info().unwrap().chain.best_number, 1);
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
Expand Down Expand Up @@ -1444,11 +1461,11 @@ pub(crate) mod tests {

// G -> A1
let a1 = client.new_block().unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a1.clone()).unwrap();
client.import(BlockOrigin::Own, a1.clone()).unwrap();

// A1 -> A2
let a2 = client.new_block().unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a2.clone()).unwrap();
client.import(BlockOrigin::Own, a2.clone()).unwrap();

let genesis_hash = client.info().unwrap().chain.genesis_hash;

Expand All @@ -1473,23 +1490,23 @@ pub(crate) mod tests {

// G -> A1
let a1 = client.new_block().unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a1.clone()).unwrap();
client.import(BlockOrigin::Own, a1.clone()).unwrap();

// A1 -> A2
let a2 = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a2.clone()).unwrap();
client.import(BlockOrigin::Own, a2.clone()).unwrap();

// A2 -> A3
let a3 = client.new_block_at(&BlockId::Hash(a2.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a3.clone()).unwrap();
client.import(BlockOrigin::Own, a3.clone()).unwrap();

// A3 -> A4
let a4 = client.new_block_at(&BlockId::Hash(a3.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a4.clone()).unwrap();
client.import(BlockOrigin::Own, a4.clone()).unwrap();

// A4 -> A5
let a5 = client.new_block_at(&BlockId::Hash(a4.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, a5.clone()).unwrap();
client.import(BlockOrigin::Own, a5.clone()).unwrap();

// A1 -> B2
let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap();
Expand All @@ -1501,15 +1518,15 @@ pub(crate) mod tests {
nonce: 0,
}).unwrap();
let b2 = builder.bake().unwrap();
client.justify_and_import(BlockOrigin::Own, b2.clone()).unwrap();
client.import(BlockOrigin::Own, b2.clone()).unwrap();

// B2 -> B3
let b3 = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, b3.clone()).unwrap();
client.import(BlockOrigin::Own, b3.clone()).unwrap();

// B3 -> B4
let b4 = client.new_block_at(&BlockId::Hash(b3.hash())).unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::Own, b4.clone()).unwrap();
client.import(BlockOrigin::Own, b4.clone()).unwrap();

// // B2 -> C3
let mut builder = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap();
Expand All @@ -1521,7 +1538,7 @@ pub(crate) mod tests {
nonce: 1,
}).unwrap();
let c3 = builder.bake().unwrap();
client.justify_and_import(BlockOrigin::Own, c3.clone()).unwrap();
client.import(BlockOrigin::Own, c3.clone()).unwrap();

// A1 -> D2
let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap();
Expand All @@ -1533,7 +1550,7 @@ pub(crate) mod tests {
nonce: 0,
}).unwrap();
let d2 = builder.bake().unwrap();
client.justify_and_import(BlockOrigin::Own, d2.clone()).unwrap();
client.import(BlockOrigin::Own, d2.clone()).unwrap();

assert_eq!(client.info().unwrap().chain.best_hash, a5.hash());

Expand Down Expand Up @@ -1686,4 +1703,44 @@ pub(crate) mod tests {
}
}
}

#[test]
fn import_with_justification() {
use test_client::blockchain::Backend;

let client = test_client::new();

// G -> A1
let a1 = client.new_block().unwrap().bake().unwrap();
client.import(BlockOrigin::Own, a1.clone()).unwrap();

// A1 -> A2
let a2 = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap().bake().unwrap();
client.import(BlockOrigin::Own, a2.clone()).unwrap();

// A2 -> A3
let justification = vec![1, 2, 3];
let a3 = client.new_block_at(&BlockId::Hash(a2.hash())).unwrap().bake().unwrap();
client.import_justified(BlockOrigin::Own, a3.clone(), justification.clone()).unwrap();

assert_eq!(
client.backend().blockchain().last_finalized().unwrap(),
a3.hash(),
);

assert_eq!(
client.backend().blockchain().justification(BlockId::Hash(a3.hash())).unwrap(),
Some(justification),
);

assert_eq!(
client.backend().blockchain().justification(BlockId::Hash(a1.hash())).unwrap(),
None,
);

assert_eq!(
client.backend().blockchain().justification(BlockId::Hash(a2.hash())).unwrap(),
None,
);
}
}
Loading