mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-08 14:48:01 +00:00
cb7527d2b2
* babe: initial implementation of secondary slots * babe: validate secondary slot author * babe: implement weight based fork choice * babe: remove unused * aura: cleanup unused imports * babe: pass in parent weight when authoring and verifying * babe: use epoch randomness for picking secondary slot authors * babe: fix tests * babe: fix wasm build * babe: node-side code for disabling secondary slots * babe: allow enabling/disabling secondary slots from runtime * babe: fix test * babe: use blake2_256 for secondary slot assignment * babe: run block initialization in should_end_session * node: increase slot duration to 6s * babe: add docs * node: bump spec_version * Apply suggestions from code review Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * babe: simplify secondary slot assignment calculation * babe: remove unnecessary comment * node: bump spec_version * babe: fix bad merge
371 lines
11 KiB
Rust
371 lines
11 KiB
Rust
// Copyright 2019 Parity Technologies (UK) Ltd.
|
|
// This file is part of Substrate.
|
|
|
|
// Substrate is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Substrate is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! BABE testsuite
|
|
|
|
// FIXME #2532: need to allow deprecated until refactor is done
|
|
// https://github.com/paritytech/substrate/issues/2532
|
|
#![allow(deprecated)]
|
|
use super::*;
|
|
|
|
use babe_primitives::AuthorityPair;
|
|
use client::{LongestChain, block_builder::BlockBuilder};
|
|
use consensus_common::NoNetwork as DummyOracle;
|
|
use network::test::*;
|
|
use network::test::{Block as TestBlock, PeersClient};
|
|
use sr_primitives::{generic::DigestItem, traits::{Block as BlockT, DigestFor}};
|
|
use network::config::ProtocolConfig;
|
|
use tokio::runtime::current_thread;
|
|
use keyring::sr25519::Keyring;
|
|
use client::BlockchainEvents;
|
|
use test_client;
|
|
use log::debug;
|
|
use std::{time::Duration, borrow::Borrow, cell::RefCell};
|
|
|
|
type Item = DigestItem<Hash>;
|
|
|
|
type Error = client::error::Error;
|
|
|
|
type TestClient = client::Client<
|
|
test_client::Backend,
|
|
test_client::Executor,
|
|
TestBlock,
|
|
test_client::runtime::RuntimeApi,
|
|
>;
|
|
|
|
struct DummyFactory(Arc<TestClient>);
|
|
struct DummyProposer(u64, Arc<TestClient>);
|
|
|
|
impl Environment<TestBlock> for DummyFactory {
|
|
type Proposer = DummyProposer;
|
|
type Error = Error;
|
|
|
|
fn init(&mut self, parent_header: &<TestBlock as BlockT>::Header)
|
|
-> Result<DummyProposer, Error>
|
|
{
|
|
Ok(DummyProposer(parent_header.number + 1, self.0.clone()))
|
|
}
|
|
}
|
|
|
|
impl Proposer<TestBlock> for DummyProposer {
|
|
type Error = Error;
|
|
type Create = future::Ready<Result<TestBlock, Error>>;
|
|
|
|
fn propose(
|
|
&mut self,
|
|
_: InherentData,
|
|
digests: DigestFor<TestBlock>,
|
|
_: Duration,
|
|
) -> Self::Create {
|
|
future::ready(self.1.new_block(digests).unwrap().bake().map_err(|e| e.into()))
|
|
}
|
|
}
|
|
|
|
type Mutator = Arc<dyn for<'r> Fn(&'r mut TestHeader) + Send + Sync>;
|
|
|
|
thread_local! {
|
|
static MUTATOR: RefCell<Mutator> = RefCell::new(Arc::new(|_|()));
|
|
}
|
|
|
|
pub struct BabeTestNet {
|
|
peers: Vec<Peer<(), DummySpecialization>>,
|
|
}
|
|
|
|
type TestHeader = <TestBlock as BlockT>::Header;
|
|
type TestExtrinsic = <TestBlock as BlockT>::Extrinsic;
|
|
|
|
pub struct TestVerifier {
|
|
inner: BabeVerifier<
|
|
test_client::Backend,
|
|
test_client::Executor,
|
|
TestBlock,
|
|
test_client::runtime::RuntimeApi,
|
|
PeersFullClient,
|
|
(),
|
|
>,
|
|
mutator: Mutator,
|
|
}
|
|
|
|
impl Verifier<TestBlock> for TestVerifier {
|
|
/// Verify the given data and return the BlockImportParams and an optional
|
|
/// new set of validators to import. If not, err with an Error-Message
|
|
/// presented to the User in the logs.
|
|
fn verify(
|
|
&mut self,
|
|
origin: BlockOrigin,
|
|
mut header: TestHeader,
|
|
justification: Option<Justification>,
|
|
body: Option<Vec<TestExtrinsic>>,
|
|
) -> Result<(BlockImportParams<TestBlock>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
|
|
let cb: &(dyn Fn(&mut TestHeader) + Send + Sync) = self.mutator.borrow();
|
|
cb(&mut header);
|
|
Ok(self.inner.verify(origin, header, justification, body).expect("verification failed!"))
|
|
}
|
|
}
|
|
|
|
impl TestNetFactory for BabeTestNet {
|
|
type Specialization = DummySpecialization;
|
|
type Verifier = TestVerifier;
|
|
type PeerData = ();
|
|
|
|
/// Create new test network with peers and given config.
|
|
fn from_config(_config: &ProtocolConfig) -> Self {
|
|
debug!(target: "babe", "Creating test network from config");
|
|
BabeTestNet {
|
|
peers: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// KLUDGE: this function gets the mutator from thread-local storage.
|
|
fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig)
|
|
-> Self::Verifier
|
|
{
|
|
let client = client.as_full().expect("only full clients are used in test");
|
|
trace!(target: "babe", "Creating a verifier");
|
|
let config = Config::get_or_compute(&*client)
|
|
.expect("slot duration available");
|
|
let inherent_data_providers = InherentDataProviders::new();
|
|
register_babe_inherent_data_provider(
|
|
&inherent_data_providers,
|
|
config.get()
|
|
).expect("Registers babe inherent data provider");
|
|
trace!(target: "babe", "Provider registered");
|
|
|
|
TestVerifier {
|
|
inner: BabeVerifier {
|
|
client: client.clone(),
|
|
api: client,
|
|
inherent_data_providers,
|
|
config,
|
|
time_source: Default::default(),
|
|
transaction_pool : Default::default(),
|
|
},
|
|
mutator: MUTATOR.with(|s| s.borrow().clone()),
|
|
}
|
|
}
|
|
|
|
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, DummySpecialization> {
|
|
trace!(target: "babe", "Retreiving a peer");
|
|
&mut self.peers[i]
|
|
}
|
|
|
|
fn peers(&self) -> &Vec<Peer<Self::PeerData, DummySpecialization>> {
|
|
trace!(target: "babe", "Retreiving peers");
|
|
&self.peers
|
|
}
|
|
|
|
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData, DummySpecialization>>)>(
|
|
&mut self,
|
|
closure: F,
|
|
) {
|
|
closure(&mut self.peers);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn rejects_empty_block() {
|
|
env_logger::try_init().unwrap();
|
|
let mut net = BabeTestNet::new(3);
|
|
let block_builder = |builder: BlockBuilder<_, _>| {
|
|
builder.bake().unwrap()
|
|
};
|
|
net.mut_peers(|peer| {
|
|
peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder);
|
|
})
|
|
}
|
|
|
|
fn run_one_test() {
|
|
let _ = env_logger::try_init();
|
|
let net = BabeTestNet::new(3);
|
|
|
|
let peers = &[
|
|
(0, "//Alice"),
|
|
(1, "//Bob"),
|
|
(2, "//Charlie"),
|
|
];
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
let mut import_notifications = Vec::new();
|
|
let mut runtime = current_thread::Runtime::new().unwrap();
|
|
let mut keystore_paths = Vec::new();
|
|
for (peer_id, seed) in peers {
|
|
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
|
let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
|
|
keystore.write().insert_ephemeral_from_seed::<AuthorityPair>(seed).expect("Generates authority key");
|
|
keystore_paths.push(keystore_path);
|
|
|
|
let client = net.lock().peer(*peer_id).client().as_full().unwrap();
|
|
let environ = DummyFactory(client.clone());
|
|
import_notifications.push(
|
|
client.import_notification_stream()
|
|
.take_while(|n| future::ready(!(n.origin != BlockOrigin::Own && n.header.number() < &5)))
|
|
.for_each(move |_| future::ready(()))
|
|
);
|
|
|
|
let config = Config::get_or_compute(&*client).expect("slot duration available");
|
|
|
|
let inherent_data_providers = InherentDataProviders::new();
|
|
register_babe_inherent_data_provider(
|
|
&inherent_data_providers, config.get()
|
|
).expect("Registers babe inherent data provider");
|
|
|
|
#[allow(deprecated)]
|
|
let select_chain = LongestChain::new(client.backend().clone());
|
|
|
|
runtime.spawn(start_babe(BabeParams {
|
|
config,
|
|
block_import: client.clone(),
|
|
select_chain,
|
|
client,
|
|
env: environ,
|
|
sync_oracle: DummyOracle,
|
|
inherent_data_providers,
|
|
force_authoring: false,
|
|
time_source: Default::default(),
|
|
keystore,
|
|
}).expect("Starts babe"));
|
|
}
|
|
|
|
runtime.spawn(futures01::future::poll_fn(move || {
|
|
net.lock().poll();
|
|
Ok::<_, ()>(futures01::Async::NotReady::<()>)
|
|
}));
|
|
|
|
runtime.block_on(future::join_all(import_notifications)
|
|
.map(|_| Ok::<(), ()>(())).compat()).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn authoring_blocks() { run_one_test() }
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn rejects_missing_inherent_digest() {
|
|
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
|
|
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
|
|
header.digest_mut().logs = v.into_iter()
|
|
.filter(|v| v.as_babe_pre_digest().is_none())
|
|
.collect()
|
|
}));
|
|
run_one_test()
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn rejects_missing_seals() {
|
|
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
|
|
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
|
|
header.digest_mut().logs = v.into_iter()
|
|
.filter(|v| v.as_babe_seal().is_none())
|
|
.collect()
|
|
}));
|
|
run_one_test()
|
|
}
|
|
|
|
// TODO: this test assumes that the test runtime will trigger epoch changes
|
|
// which isn't the case since it doesn't include the session module.
|
|
#[test]
|
|
#[should_panic]
|
|
#[ignore]
|
|
fn rejects_missing_consensus_digests() {
|
|
MUTATOR.with(|s| *s.borrow_mut() = Arc::new(move |header: &mut TestHeader| {
|
|
let v = std::mem::replace(&mut header.digest_mut().logs, vec![]);
|
|
header.digest_mut().logs = v.into_iter()
|
|
.filter(|v| v.as_babe_epoch().is_none())
|
|
.collect()
|
|
}));
|
|
run_one_test()
|
|
}
|
|
|
|
#[test]
|
|
fn wrong_consensus_engine_id_rejected() {
|
|
let _ = env_logger::try_init();
|
|
let sig = AuthorityPair::generate().0.sign(b"");
|
|
let bad_seal: Item = DigestItem::Seal([0; 4], sig.to_vec());
|
|
assert!(bad_seal.as_babe_pre_digest().is_none());
|
|
assert!(bad_seal.as_babe_seal().is_none())
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_pre_digest_rejected() {
|
|
let _ = env_logger::try_init();
|
|
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, [0; 64].to_vec());
|
|
assert!(bad_seal.as_babe_pre_digest().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn sig_is_not_pre_digest() {
|
|
let _ = env_logger::try_init();
|
|
let sig = AuthorityPair::generate().0.sign(b"");
|
|
let bad_seal: Item = DigestItem::Seal(BABE_ENGINE_ID, sig.to_vec());
|
|
assert!(bad_seal.as_babe_pre_digest().is_none());
|
|
assert!(bad_seal.as_babe_seal().is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn can_author_block() {
|
|
let _ = env_logger::try_init();
|
|
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
|
|
let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
|
|
let pair = keystore.write().insert_ephemeral_from_seed::<AuthorityPair>("//Alice")
|
|
.expect("Generates authority pair");
|
|
|
|
let mut i = 0;
|
|
let mut epoch = Epoch {
|
|
start_slot: 0,
|
|
authorities: vec![(pair.public(), 1)],
|
|
randomness: [0; 32],
|
|
epoch_index: 1,
|
|
duration: 100,
|
|
secondary_slots: true,
|
|
};
|
|
|
|
let parent_weight = 0;
|
|
|
|
// with secondary slots enabled it should never be empty
|
|
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
|
|
None => i += 1,
|
|
Some(s) => debug!(target: "babe", "Authored block {:?}", s.0),
|
|
}
|
|
|
|
// otherwise with only vrf-based primary slots we might need to try a couple
|
|
// of times.
|
|
epoch.secondary_slots = false;
|
|
loop {
|
|
match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) {
|
|
None => i += 1,
|
|
Some(s) => {
|
|
debug!(target: "babe", "Authored block {:?}", s.0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn authorities_call_works() {
|
|
let _ = env_logger::try_init();
|
|
let client = test_client::new();
|
|
|
|
assert_eq!(client.info().chain.best_number, 0);
|
|
assert_eq!(epoch(&client, &BlockId::Number(0)).unwrap().into_regular().unwrap().authorities, vec![
|
|
(Keyring::Alice.public().into(), 1),
|
|
(Keyring::Bob.public().into(), 1),
|
|
(Keyring::Charlie.public().into(), 1),
|
|
]);
|
|
}
|