Offline fallback for GRANDPA (#1619)

Co-authored-by: André Silva <andre.beat@gmail.com>

* skeleton for finality tracker

* dispatch events when nothing finalized for a long time

* begin integrating finality tracker into grandpa

* add delay field to pending change

* add has_api_with function to sr_version for querying APIs

* partially integrate new force changes into grandpa

* implement forced changes

* get srml-grandpa compiling

* Update core/finality-grandpa/src/authorities.rs

Co-Authored-By: rphmeier <rphmeier@gmail.com>

* Update core/finality-grandpa/src/authorities.rs

Co-Authored-By: rphmeier <rphmeier@gmail.com>

* Update core/finality-grandpa/src/authorities.rs

Co-Authored-By: rphmeier <rphmeier@gmail.com>

* remove explicit dependence on CoreApi

* increase node runtime version

* integrate grandpa forced changes into node runtime

* add some tests to finality-tracker

* integrate finality tracking into node-runtime

* test forced-change logic

* test forced changes in the authority-set handler

* kill some unneeded bounds in client

* test forced-changes in finality-grandpa and fix logic

* build wasm and finality-tracker is no-std

* restart voter on forced change

* allow returning custom error type from lock_import_and_run

* extract out most DB logic to aux_schema and use atomic client ops

* unify authority set writing

* implement set pausing

* bump runtime version

* note on DB when we pause.

* core: grandpa: integrate forced changes with multiple pending standard changes

* core: grandpa: fix AuthoritySet tests

* runtime: bump impl_version

* core: clear pending justification requests after forced change import

* srml: finality-tracker: use FinalizedInherentData

* core: log requests for clearing justification requests

* core, node: update runtimes

* core: grandpa: fix tests

* core: grandpa: remove todos and add comments

* core: grandpa: use has_api_with from ApiExt

* core: fix tests

* core: grandpa: remove unnecessary mut modifier

* core: replace PostImportActions bitflags with struct

* core: grandpa: restrict genesis on forced authority set change

* core: grandpa: add more docs

* core: grandpa: prevent safety violations in Environment::finalize_block

* core: grandpa: register finality tracker inherent data provider

* core: grandpa: fix tests

* node: update runtime blobs

* core: grandpa: remove outdated todo

* core: aura: fix typo in log message

* core: grandpa: check re-finalization is on canonical chain

* srml: finality-tracker: fix initialization

* node: update runtime wasm

* srml: finality-tracker: don't re-initialize config keys
This commit is contained in:
Robert Habermeier
2019-03-05 16:41:35 +01:00
committed by André Silva
parent 128d164f2b
commit dfb48a2405
31 changed files with 2217 additions and 516 deletions
+102 -9
View File
@@ -29,8 +29,7 @@ use client::{
runtime_api::{Core, RuntimeVersion, ApiExt},
};
use test_client::{self, runtime::BlockNumber};
use parity_codec::Decode;
use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportBlock, ImportResult};
use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult};
use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport};
use std::collections::{HashMap, HashSet};
use std::result;
@@ -40,6 +39,7 @@ use runtime_primitives::ExecutionContext;
use substrate_primitives::NativeOrEncoded;
use authorities::AuthoritySet;
use consensus_changes::ConsensusChanges;
type PeerData =
Mutex<
@@ -240,6 +240,7 @@ impl Network<Block> for MessageRouting {
struct TestApi {
genesis_authorities: Vec<(Ed25519AuthorityId, u64)>,
scheduled_changes: Arc<Mutex<HashMap<Hash, ScheduledChange<BlockNumber>>>>,
forced_changes: Arc<Mutex<HashMap<Hash, (BlockNumber, ScheduledChange<BlockNumber>)>>>,
}
impl TestApi {
@@ -247,6 +248,7 @@ impl TestApi {
TestApi {
genesis_authorities,
scheduled_changes: Arc::new(Mutex::new(HashMap::new())),
forced_changes: Arc::new(Mutex::new(HashMap::new())),
}
}
}
@@ -349,6 +351,24 @@ impl GrandpaApi<Block> for RuntimeApi {
// extrinsics.
Ok(self.inner.scheduled_changes.lock().get(&parent_hash).map(|c| c.clone())).map(NativeOrEncoded::Native)
}
fn grandpa_forced_change_runtime_api_impl(
&self,
at: &BlockId<Block>,
_: ExecutionContext,
_: Option<(&DigestFor<Block>)>,
_: Vec<u8>,
)
-> Result<NativeOrEncoded<Option<(NumberFor<Block>, ScheduledChange<NumberFor<Block>>)>>> {
let parent_hash = match at {
&BlockId::Hash(at) => at,
_ => panic!("not requested by block hash!!"),
};
// we take only scheduled changes at given block number where there are no
// extrinsics.
Ok(self.inner.forced_changes.lock().get(&parent_hash).map(|c| c.clone())).map(NativeOrEncoded::Native)
}
}
const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500);
@@ -361,7 +381,14 @@ fn make_ids(keys: &[Keyring]) -> Vec<(Ed25519AuthorityId, u64)> {
.collect()
}
fn run_to_completion(blocks: u64, net: Arc<Mutex<GrandpaTestNet>>, peers: &[Keyring]) -> u64 {
// run the voters to completion. provide a closure to be invoked after
// the voters are spawned but before blocking on them.
fn run_to_completion_with<F: FnOnce()>(
blocks: u64,
net: Arc<Mutex<GrandpaTestNet>>,
peers: &[Keyring],
before_waiting: F,
) -> u64 {
use parking_lot::RwLock;
let mut finality_notifications = Vec::new();
@@ -402,6 +429,7 @@ fn run_to_completion(blocks: u64, net: Arc<Mutex<GrandpaTestNet>>, peers: &[Keyr
},
link,
MessageRouting::new(net.clone(), peer_id),
InherentDataProviders::new(),
futures::empty(),
).expect("all in order with client and network");
@@ -425,6 +453,8 @@ fn run_to_completion(blocks: u64, net: Arc<Mutex<GrandpaTestNet>>, peers: &[Keyr
.map(|_| ())
.map_err(|_| ());
(before_waiting)();
runtime.block_on(wait_for.select(drive_to_completion).map_err(|_| ())).unwrap();
let highest_finalized = *highest_finalized.read();
@@ -432,8 +462,13 @@ fn run_to_completion(blocks: u64, net: Arc<Mutex<GrandpaTestNet>>, peers: &[Keyr
highest_finalized
}
fn run_to_completion(blocks: u64, net: Arc<Mutex<GrandpaTestNet>>, peers: &[Keyring]) -> u64 {
run_to_completion_with(blocks, net, peers, || {})
}
#[test]
fn finalize_3_voters_no_observers() {
let _ = env_logger::try_init();
let peers = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
let voters = make_ids(peers);
@@ -495,6 +530,7 @@ fn finalize_3_voters_1_observer() {
},
link,
MessageRouting::new(net.clone(), peer_id),
InherentDataProviders::new(),
futures::empty(),
).expect("all in order with client and network");
@@ -552,8 +588,9 @@ fn transition_3_voters_twice_1_observer() {
assert_eq!(peer.client().info().unwrap().chain.best_number, 1,
"Peer #{} failed to sync", i);
let set_raw = peer.client().backend().get_aux(crate::AUTHORITY_SET_KEY).unwrap().unwrap();
let set = AuthoritySet::<Hash, BlockNumber>::decode(&mut &set_raw[..]).unwrap();
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
&**peer.client().backend()
).unwrap();
assert_eq!(set.current(), (0, make_ids(peers_a).as_slice()));
assert_eq!(set.pending_changes().count(), 0);
@@ -638,8 +675,9 @@ fn transition_3_voters_twice_1_observer() {
.take_while(|n| Ok(n.header.number() < &30))
.for_each(move |_| Ok(()))
.map(move |()| {
let set_raw = client.backend().get_aux(crate::AUTHORITY_SET_KEY).unwrap().unwrap();
let set = AuthoritySet::<Hash, BlockNumber>::decode(&mut &set_raw[..]).unwrap();
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
&**client.backend()
).unwrap();
assert_eq!(set.current(), (2, make_ids(peers_c).as_slice()));
assert_eq!(set.pending_changes().count(), 0);
@@ -654,6 +692,7 @@ fn transition_3_voters_twice_1_observer() {
},
link,
MessageRouting::new(net.clone(), peer_id),
InherentDataProviders::new(),
futures::empty(),
).expect("all in order with client and network");
@@ -784,7 +823,7 @@ fn sync_justifications_on_change_blocks() {
#[test]
fn finalizes_multiple_pending_changes_in_order() {
env_logger::init();
let _ = env_logger::try_init();
let peers_a = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
let peers_b = &[Keyring::Dave, Keyring::Eve, Keyring::Ferdie];
@@ -865,6 +904,60 @@ fn doesnt_vote_on_the_tip_of_the_chain() {
assert_eq!(highest, 75);
}
#[test]
fn force_change_to_new_set() {
// two of these guys are offline.
let genesis_authorities = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie, Keyring::One, Keyring::Two];
let peers_a = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
let api = TestApi::new(make_ids(genesis_authorities));
let voters = make_ids(peers_a);
let normal_transitions = api.scheduled_changes.clone();
let forced_transitions = api.forced_changes.clone();
let net = GrandpaTestNet::new(api, 3);
let net = Arc::new(Mutex::new(net));
let runner_net = net.clone();
let add_blocks = move || {
net.lock().peer(0).push_blocks(1, false);
{
// add a forced transition at block 12.
let parent_hash = net.lock().peer(0).client().info().unwrap().chain.best_hash;
forced_transitions.lock().insert(parent_hash, (0, ScheduledChange {
next_authorities: voters.clone(),
delay: 10,
}));
// add a normal transition too to ensure that forced changes take priority.
normal_transitions.lock().insert(parent_hash, ScheduledChange {
next_authorities: make_ids(genesis_authorities),
delay: 5,
});
}
net.lock().peer(0).push_blocks(25, false);
net.lock().sync();
for (i, peer) in net.lock().peers().iter().enumerate() {
assert_eq!(peer.client().info().unwrap().chain.best_number, 26,
"Peer #{} failed to sync", i);
let set: AuthoritySet<Hash, BlockNumber> = crate::aux_schema::load_authorities(
&**peer.client().backend()
).unwrap();
assert_eq!(set.current(), (1, voters.as_slice()));
assert_eq!(set.pending_changes().count(), 0);
}
};
// it will only finalize if the forced transition happens.
// we add_blocks after the voters are spawned because otherwise
// the link-halfs have the wrong AuthoritySet
run_to_completion_with(25, runner_net, peers_a, add_blocks);
}
#[test]
fn allows_reimporting_change_blocks() {
let peers_a = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
@@ -899,7 +992,7 @@ fn allows_reimporting_change_blocks() {
assert_eq!(
block_import.import_block(block(), None).unwrap(),
ImportResult::NeedsJustification
ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false }),
);
assert_eq!(