grandpa: Voter persistence and upgrade to finality-grandpa v0.7 (#2139)

* core: grandpa: migrate to grandpa 0.7

* core: grandpa: store current round votes and load them on startup

* core: grandpa: resend old persisted votes for the current round

* core: grandpa: store base and votes for last completed round

* core: grandpa: fix latest grandpa 0.7 changes

* core: grandpa: update to grandpa 0.7.1

* core: grandpa: persist votes for last two completed rounds

* core: grandpa: simplify VoterSetState usage

* core: grandpa: use Environment::update_voter_set_state

* core: grandpa: fix aux_schema test

* core: grandpa: add docs

* core: grandpa: add note about environment assumption

* core: grandpa: don't update voter set state on ignored votes

* core: grandpa: add test for v1 -> v2 aux_schema migration

* core: grandpa: add test for voter vote persistence

* core: grandpa: use grandpa 0.7.1 from crates.io

* core: grandpa: use try_init in test

* core: grandpa: add comment about block_import in test

* core: grandpa: avoid cloning HasVoted

* core: grandpa: add missing docs

* core: grandpa: cleanup up can_propose/prevote/precommit
This commit is contained in:
André Silva
2019-04-08 12:50:34 +01:00
committed by Robert Habermeier
parent ed3ae4ac39
commit a1e15ae55a
11 changed files with 1020 additions and 190 deletions
@@ -1015,3 +1015,196 @@ fn test_bad_justification() {
ImportResult::AlreadyInChain
);
}
#[test]
fn voter_persists_its_votes() {
use std::iter::FromIterator;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures::future;
use futures::sync::mpsc;
let _ = env_logger::try_init();
// we have two authorities but we'll only be running the voter for alice
// we are going to be listening for the prevotes it casts
let peers = &[AuthorityKeyring::Alice, AuthorityKeyring::Bob];
let voters = make_ids(peers);
// alice has a chain with 20 blocks
let mut net = GrandpaTestNet::new(TestApi::new(voters.clone()), 2);
net.peer(0).push_blocks(20, false);
net.sync();
assert_eq!(net.peer(0).client().info().unwrap().chain.best_number, 20,
"Peer #{} failed to sync", 0);
let mut runtime = current_thread::Runtime::new().unwrap();
let client = net.peer(0).client().clone();
let net = Arc::new(Mutex::new(net));
let (voter_tx, voter_rx) = mpsc::unbounded::<()>();
// startup a grandpa voter for alice but also listen for messages on a
// channel. whenever a message is received the voter is restarted. when the
// sender is dropped the voter is stopped.
{
let net = net.clone();
let voter = future::loop_fn(voter_rx, move |rx| {
let (_block_import, _, link) = net.lock().make_block_import(client.clone());
let link = link.lock().take().unwrap();
let mut voter = run_grandpa(
Config {
gossip_duration: TEST_GOSSIP_DURATION,
justification_period: 32,
local_key: Some(Arc::new(peers[0].clone().into())),
name: Some(format!("peer#{}", 0)),
},
link,
MessageRouting::new(net.clone(), 0),
InherentDataProviders::new(),
futures::empty(),
).expect("all in order with client and network");
let voter = future::poll_fn(move || {
// we need to keep the block_import alive since it owns the
// sender for the voter commands channel, if that gets dropped
// then the voter will stop
let _block_import = _block_import.clone();
voter.poll()
});
voter.select2(rx.into_future()).then(|res| match res {
Ok(future::Either::A(x)) => {
panic!("voter stopped unexpectedly: {:?}", x);
},
Ok(future::Either::B(((Some(()), rx), _))) => {
Ok(future::Loop::Continue(rx))
},
Ok(future::Either::B(((None, _), _))) => {
Ok(future::Loop::Break(()))
},
Err(future::Either::A(err)) => {
panic!("unexpected error: {:?}", err);
},
Err(future::Either::B(..)) => {
// voter_rx dropped, stop the voter.
Ok(future::Loop::Break(()))
},
})
});
runtime.spawn(voter);
}
let (exit_tx, exit_rx) = futures::sync::oneshot::channel::<()>();
// create the communication layer for bob, but don't start any
// voter. instead we'll listen for the prevote that alice casts
// and cast our own manually
{
let config = Config {
gossip_duration: TEST_GOSSIP_DURATION,
justification_period: 32,
local_key: Some(Arc::new(peers[1].clone().into())),
name: Some(format!("peer#{}", 1)),
};
let routing = MessageRouting::new(net.clone(), 1);
let network = communication::NetworkBridge::new(routing, config.clone());
let (round_rx, round_tx) = network.round_communication(
communication::Round(1),
communication::SetId(0),
Arc::new(VoterSet::from_iter(voters)),
Some(config.local_key.unwrap()),
HasVoted::No,
);
let round_tx = Arc::new(Mutex::new(round_tx));
let exit_tx = Arc::new(Mutex::new(Some(exit_tx)));
let net = net.clone();
let state = AtomicUsize::new(0);
runtime.spawn(round_rx.for_each(move |signed| {
if state.compare_and_swap(0, 1, Ordering::SeqCst) == 0 {
// the first message we receive should be a prevote from alice.
let prevote = match signed.message {
grandpa::Message::Prevote(prevote) => prevote,
_ => panic!("voter should prevote."),
};
// its chain has 20 blocks and the voter targets 3/4 of the
// unfinalized chain, so the vote should be for block 15
assert!(prevote.target_number == 15);
// we push 20 more blocks to alice's chain
net.lock().peer(0).push_blocks(20, false);
net.lock().sync();
assert_eq!(net.lock().peer(0).client().info().unwrap().chain.best_number, 40,
"Peer #{} failed to sync", 0);
let block_30_hash =
net.lock().peer(0).client().backend().blockchain().hash(30).unwrap().unwrap();
// we restart alice's voter
voter_tx.unbounded_send(()).unwrap();
// and we push our own prevote for block 30
let prevote = grandpa::Prevote {
target_number: 30,
target_hash: block_30_hash,
};
round_tx.lock().start_send(grandpa::Message::Prevote(prevote)).unwrap();
} else if state.compare_and_swap(1, 2, Ordering::SeqCst) == 1 {
// the next message we receive should be our own prevote
let prevote = match signed.message {
grandpa::Message::Prevote(prevote) => prevote,
_ => panic!("We should receive our own prevote."),
};
// targeting block 30
assert!(prevote.target_number == 30);
// after alice restarts it should send its previous prevote
// therefore we won't ever receive it again since it will be a
// known message on the gossip layer
} else if state.compare_and_swap(2, 3, Ordering::SeqCst) == 2 {
// we then receive a precommit from alice for block 15
// even though we casted a prevote for block 30
let precommit = match signed.message {
grandpa::Message::Precommit(precommit) => precommit,
_ => panic!("voter should precommit."),
};
assert!(precommit.target_number == 15);
// signal exit
exit_tx.clone().lock().take().unwrap().send(()).unwrap();
}
Ok(())
}).map_err(|_| ()));
}
let net = net.clone();
let drive_to_completion = ::tokio::timer::Interval::new_interval(TEST_ROUTING_INTERVAL)
.for_each(move |_| {
net.lock().send_import_notifications();
net.lock().send_finality_notifications();
net.lock().route_fast();
Ok(())
})
.map(|_| ())
.map_err(|_| ());
let exit = exit_rx.into_future().map(|_| ()).map_err(|_| ());
runtime.block_on(drive_to_completion.select(exit).map(|_| ()).map_err(|_| ())).unwrap();
}