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

Commit

Permalink
Init store for slots-headers (#2492)
Browse files Browse the repository at this point in the history
* init store for slots

* fix: add check_equivocation to Aura/Babe

* fix tests

* fix: add pruning bound

Co-Authored-By: André Silva <andre.beat@gmail.com>

* use saturating_sub
  • Loading branch information
seerscode authored May 15, 2019
1 parent e6381a8 commit 541e9c8
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 20 deletions.
6 changes: 3 additions & 3 deletions core/consensus/aura/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ srml-consensus = { path = "../../../srml/consensus" }
srml-aura = { path = "../../../srml/aura" }
client = { package = "substrate-client", path = "../../client" }
substrate-telemetry = { path = "../../telemetry" }
consensus_common = { package = "substrate-consensus-common", path = "../common" }
authorities = { package = "substrate-consensus-authorities", path = "../authorities" }
runtime_primitives = { package = "sr-primitives", path = "../../sr-primitives" }
futures = "0.1.17"
tokio = "0.1.7"
parking_lot = "0.7.1"
error-chain = "0.12"
log = "0.4"
consensus_common = { package = "substrate-consensus-common", path = "../common" }
authorities = { package = "substrate-consensus-authorities", path = "../authorities" }
runtime_primitives = { package = "sr-primitives", path = "../../sr-primitives" }

[dev-dependencies]
keyring = { package = "substrate-keyring", path = "../../keyring" }
Expand Down
106 changes: 95 additions & 11 deletions core/consensus/aura/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use srml_aura::{
};
use substrate_telemetry::{telemetry, CONSENSUS_TRACE, CONSENSUS_DEBUG, CONSENSUS_WARN, CONSENSUS_INFO};

use slots::{CheckedHeader, SlotWorker, SlotInfo, SlotCompatible, slot_now};
use slots::{CheckedHeader, SlotWorker, SlotInfo, SlotCompatible, slot_now, check_equivocation};

pub use aura_primitives::*;
pub use consensus_common::{SyncOracle, ExtraVerification};
Expand Down Expand Up @@ -196,7 +196,7 @@ pub fn start_aura_thread<B, C, SC, E, I, P, SO, Error, OnExit>(
force_authoring: bool,
) -> Result<(), consensus_common::Error> where
B: Block + 'static,
C: ProvideRuntimeApi + ProvideCache<B> + Send + Sync + 'static,
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore + Send + Sync + 'static,
C::Api: AuthoritiesApi<B>,
SC: SelectChain<B> + Clone + 'static,
E: Environment<B, Error=Error> + Send + Sync + 'static,
Expand Down Expand Up @@ -247,7 +247,7 @@ pub fn start_aura<B, C, SC, E, I, P, SO, Error, OnExit>(
force_authoring: bool,
) -> Result<impl Future<Item=(), Error=()>, consensus_common::Error> where
B: Block,
C: ProvideRuntimeApi + ProvideCache<B>,
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore,
C::Api: AuthoritiesApi<B>,
SC: SelectChain<B> + Clone,
E: Environment<B, Error=Error>,
Expand Down Expand Up @@ -292,7 +292,7 @@ struct AuraWorker<C, E, I, P, SO> {
}

impl<B: Block, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, SO> where
C: ProvideRuntimeApi + ProvideCache<B>,
C: ProvideRuntimeApi + ProvideCache<B> + AuxStore,
C::Api: AuthoritiesApi<B>,
E: Environment<B, Error=Error>,
E::Proposer: Proposer<B, Error=Error>,
Expand Down Expand Up @@ -459,16 +459,18 @@ impl<B: Block, C, E, I, P, Error, SO> SlotWorker<B> for AuraWorker<C, E, I, P, S
/// This digest item will always return `Some` when used with `as_aura_seal`.
//
// FIXME #1018 needs misbehavior types
fn check_header<B: Block, P: Pair>(
fn check_header<C, B: Block, P: Pair>(
client: &Arc<C>,
slot_now: u64,
mut header: B::Header,
hash: B::Hash,
authorities: &[AuthorityId<P>],
allow_old_seals: bool,
) -> Result<CheckedHeader<B::Header, DigestItemFor<B>>, String>
where DigestItemFor<B>: CompatibleDigestItem<P>,
P::Public: AsRef<P::Public>,
P::Signature: Decode,
C: client::backend::AuxStore,
P::Public: AsRef<P::Public> + Encode + Decode + PartialEq + Clone,
{
let digest_item = match header.digest_mut().pop() {
Some(x) => x,
Expand Down Expand Up @@ -501,7 +503,26 @@ fn check_header<B: Block, P: Pair>(
let public = expected_author;

if P::verify(&sig, &to_sign[..], public) {
Ok(CheckedHeader::Checked(header, digest_item))
match check_equivocation::<_, _, <P as Pair>::Public>(
client,
slot_now,
slot_num,
header.clone(),
public.clone(),
) {
Ok(Some(equivocation_proof)) => {
let log_str = format!(
"Slot author is equivocating at slot {} with headers {:?} and {:?}",
slot_num,
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
info!("{}", log_str);
Err(log_str)
},
Ok(None) => Ok(CheckedHeader::Checked(header, digest_item)),
Err(e) => Err(e.to_string()),
}
} else {
Err(format!("Bad signature on {:?}", hash))
}
Expand Down Expand Up @@ -583,7 +604,7 @@ impl<B: Block> ExtraVerification<B> for NothingExtra {

#[forbid(deprecated)]
impl<B: Block, C, E, P> Verifier<B> for AuraVerifier<C, E, P> where
C: ProvideRuntimeApi + Send + Sync,
C: ProvideRuntimeApi + Send + Sync + client::backend::AuxStore,
C::Api: BlockBuilderApi<B>,
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
E: ExtraVerification<B>,
Expand Down Expand Up @@ -614,7 +635,8 @@ impl<B: Block, C, E, P> Verifier<B> for AuraVerifier<C, E, P> where

// we add one to allow for some small drift.
// FIXME #1019 in the future, alter this queue to allow deferring of headers
let checked_header = check_header::<B, P>(
let checked_header = check_header::<C, B, P>(
&self.client,
slot_now + 1,
header,
hash,
Expand Down Expand Up @@ -776,7 +798,7 @@ pub fn import_queue<B, C, E, P>(
inherent_data_providers: InherentDataProviders,
) -> Result<AuraImportQueue<B>, consensus_common::Error> where
B: Block,
C: 'static + ProvideRuntimeApi + ProvideCache<B> + Send + Sync,
C: 'static + ProvideRuntimeApi + ProvideCache<B> + Send + Sync + AuxStore,
C::Api: BlockBuilderApi<B> + AuthoritiesApi<B>,
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
E: 'static + ExtraVerification<B>,
Expand Down Expand Up @@ -821,7 +843,7 @@ pub fn import_queue_accept_old_seals<B, C, E, P>(
inherent_data_providers: InherentDataProviders,
) -> Result<AuraImportQueue<B>, consensus_common::Error> where
B: Block,
C: 'static + ProvideRuntimeApi + ProvideCache<B> + Send + Sync,
C: 'static + ProvideRuntimeApi + ProvideCache<B> + Send + Sync + AuxStore,
C::Api: BlockBuilderApi<B> + AuthoritiesApi<B>,
DigestItemFor<B>: CompatibleDigestItem<P> + DigestItem<AuthorityId=AuthorityId<P>>,
E: 'static + ExtraVerification<B>,
Expand Down Expand Up @@ -864,6 +886,9 @@ mod tests {
use primitives::sr25519;
use client::{LongestChain, BlockchainEvents};
use test_client;
use primitives::hash::H256;
use runtime_primitives::testing::{Header as HeaderTest, Digest as DigestTest, Block as RawBlock, ExtrinsicWrapper};
use slots::{MAX_SLOT_CAPACITY, PRUNING_BOUND};

type Error = client::error::Error;

Expand Down Expand Up @@ -960,6 +985,26 @@ mod tests {
}
}

fn create_header(slot_num: u64, number: u64, pair: &sr25519::Pair) -> (HeaderTest, H256) {
let mut header = HeaderTest {
parent_hash: Default::default(),
number,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: DigestTest { logs: vec![], },
};
let header_hash: H256 = header.hash();
let to_sign = (slot_num, header_hash).encode();
let signature = pair.sign(&to_sign[..]);

let item = <generic::DigestItem<_, _, _> as CompatibleDigestItem<sr25519::Pair>>::aura_seal(
slot_num,
signature,
);
header.digest_mut().push(item);
(header, header_hash)
}

#[test]
fn authoring_blocks() {
let _ = ::env_logger::try_init();
Expand Down Expand Up @@ -1042,4 +1087,43 @@ mod tests {
Keyring::Charlie.into()
]);
}

#[test]
fn check_header_works_with_equivocation() {
let client = test_client::new();
let pair = sr25519::Pair::generate();
let public = pair.public();
let authorities = vec![public.clone(), sr25519::Pair::generate().public()];

let (header1, header1_hash) = create_header(2, 1, &pair);
let (header2, header2_hash) = create_header(2, 2, &pair);
let (header3, header3_hash) = create_header(4, 2, &pair);
let (header4, header4_hash) = create_header(MAX_SLOT_CAPACITY + 4, 3, &pair);
let (header5, header5_hash) = create_header(MAX_SLOT_CAPACITY + 4, 4, &pair);
let (header6, header6_hash) = create_header(4, 3, &pair);

type B = RawBlock<ExtrinsicWrapper<u64>>;
type P = sr25519::Pair;

let c = Arc::new(client);

// It's ok to sign same headers.
assert!(check_header::<_, B, P>(&c, 2, header1.clone(), header1_hash, &authorities, false).is_ok());
assert!(check_header::<_, B, P>(&c, 3, header1, header1_hash, &authorities, false).is_ok());

// But not two different headers at the same slot.
assert!(check_header::<_, B, P>(&c, 4, header2, header2_hash, &authorities, false).is_err());

// Different slot is ok.
assert!(check_header::<_, B, P>(&c, 5, header3, header3_hash, &authorities, false).is_ok());

// Here we trigger pruning and save header 4.
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 2, header4, header4_hash, &authorities, false).is_ok());

// This fails because header 5 is an equivocation of header 4.
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 3, header5, header5_hash, &authorities, false).is_err());

// This is ok because we pruned the corresponding header. Shows that we are pruning.
assert!(check_header::<_, B, P>(&c, PRUNING_BOUND + 4, header6, header6_hash, &authorities, false).is_ok());
}
}
111 changes: 105 additions & 6 deletions core/consensus/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use client::{
error::Result as CResult,
backend::AuxStore,
};
use slots::CheckedHeader;
use slots::{CheckedHeader, check_equivocation};
use futures::{Future, IntoFuture, future};
use tokio::timer::Timeout;
use log::{error, warn, debug, info, trace};
Expand Down Expand Up @@ -534,7 +534,8 @@ impl<B: Block, C, E, I, Error, SO> SlotWorker<B> for BabeWorker<C, E, I, SO> whe
//
// FIXME #1018 needs misbehavior types
#[forbid(warnings)]
fn check_header<B: Block + Sized>(
fn check_header<B: Block + Sized, C: AuxStore>(
client: &Arc<C>,
slot_now: u64,
mut header: B::Header,
hash: B::Hash,
Expand All @@ -551,7 +552,7 @@ fn check_header<B: Block + Sized>(

let BabeSeal {
slot_num,
signature: LocalizedSignature {signer, signature },
signature: LocalizedSignature { signer, signature },
proof,
vrf_output,
} = digest_item.as_babe_seal().ok_or_else(|| {
Expand Down Expand Up @@ -584,8 +585,27 @@ fn check_header<B: Block + Sized>(
format!("VRF verification failed")
})?
};

if check(&inout, threshold) {
Ok(CheckedHeader::Checked(header, digest_item))
match check_equivocation(&client, slot_now, slot_num, header.clone(), signer.clone()) {
Ok(Some(equivocation_proof)) => {
let log_str = format!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
signer,
slot_num,
equivocation_proof.fst_header().hash(),
equivocation_proof.snd_header().hash(),
);
info!("{}", log_str);
Err(log_str)
},
Ok(None) => {
Ok(CheckedHeader::Checked(header, digest_item))
},
Err(e) => {
Err(e.to_string())
},
}
} else {
debug!(target: "babe", "VRF verification failed: threshold {} exceeded", threshold);
Err(format!("Validator {:?} made seal when it wasn’t its turn", signer))
Expand Down Expand Up @@ -643,7 +663,7 @@ impl<B: Block> ExtraVerification<B> for NothingExtra {
}

impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
C: ProvideRuntimeApi + Send + Sync,
C: ProvideRuntimeApi + Send + Sync + AuxStore,
C::Api: BlockBuilderApi<B>,
DigestItemFor<B>: CompatibleDigestItem + DigestItem<AuthorityId=Public>,
E: ExtraVerification<B>,
Expand Down Expand Up @@ -683,7 +703,8 @@ impl<B: Block, C, E> Verifier<B> for BabeVerifier<C, E> where
// we add one to allow for some small drift.
// FIXME #1019 in the future, alter this queue to allow deferring of
// headers
let checked_header = check_header::<B>(
let checked_header = check_header::<B, C>(
&self.client,
slot_now + 1,
header,
hash,
Expand Down Expand Up @@ -871,6 +892,10 @@ mod tests {
use futures::stream::Stream;
use log::debug;
use std::time::Duration;
use test_client::AuthorityKeyring;
use primitives::hash::H256;
use runtime_primitives::testing::{Header as HeaderTest, Digest as DigestTest, Block as RawBlock, ExtrinsicWrapper};
use slots::{MAX_SLOT_CAPACITY, PRUNING_BOUND};

type Error = client::error::Error;

Expand Down Expand Up @@ -975,6 +1000,40 @@ mod tests {
}
}

fn create_header(slot_num: u64, number: u64, pair: &sr25519::Pair) -> (HeaderTest, H256) {
let mut header = HeaderTest {
parent_hash: Default::default(),
number,
state_root: Default::default(),
extrinsics_root: Default::default(),
digest: DigestTest { logs: vec![], },
};

let transcript = make_transcript(
Default::default(),
slot_num,
Default::default(),
0,
);

let (inout, proof, _batchable_proof) = get_keypair(&pair).vrf_sign_n_check(transcript, |inout| check(inout, u64::MAX)).unwrap();
let pre_hash: H256 = header.hash();
let to_sign = (slot_num, pre_hash, proof.to_bytes()).encode();
let signature = pair.sign(&to_sign[..]);
let item = <generic::DigestItem<_, _, _> as CompatibleDigestItem>::babe_seal(BabeSeal {
proof,
signature: LocalizedSignature {
signature,
signer: pair.public(),
},
slot_num,
vrf_output: inout.to_output(),
});

header.digest_mut().push(item);
(header, pre_hash)
}

#[test]
fn can_serialize_block() {
drop(env_logger::try_init());
Expand Down Expand Up @@ -1104,4 +1163,44 @@ mod tests {
Keyring::Charlie.into()
]);
}

#[test]
fn check_header_works_with_equivocation() {
let client = test_client::new();
let pair = sr25519::Pair::generate();
let public = pair.public();
let authorities = vec![public.clone(), sr25519::Pair::generate().public()];

let (header1, header1_hash) = create_header(2, 1, &pair);
let (header2, header2_hash) = create_header(2, 2, &pair);
let (header3, header3_hash) = create_header(4, 2, &pair);
let (header4, header4_hash) = create_header(MAX_SLOT_CAPACITY + 4, 3, &pair);
let (header5, header5_hash) = create_header(MAX_SLOT_CAPACITY + 4, 4, &pair);
let (header6, header6_hash) = create_header(4, 3, &pair);

let c = Arc::new(client);
let max = u64::MAX;

type B = RawBlock<ExtrinsicWrapper<u64>>;
type P = sr25519::Pair;

// It's ok to sign same headers.
assert!(check_header::<B, _>(&c, 2, header1.clone(), header1_hash, &authorities, max).is_ok());
assert!(check_header::<B, _>(&c, 3, header1, header1_hash, &authorities, max).is_ok());

// But not two different headers at the same slot.
assert!(check_header::<B, _>(&c, 4, header2, header2_hash, &authorities, max).is_err());

// Different slot is ok.
assert!(check_header::<B, _>(&c, 5, header3, header3_hash, &authorities, max).is_ok());

// Here we trigger pruning and save header 4.
assert!(check_header::<B, _>(&c, PRUNING_BOUND + 2, header4, header4_hash, &authorities, max).is_ok());

// This fails because header 5 is an equivocation of header 4.
assert!(check_header::<B, _>(&c, PRUNING_BOUND + 3, header5, header5_hash, &authorities, max).is_err());

// This is ok because we pruned the corresponding header. Shows that we are pruning.
assert!(check_header::<B, _>(&c, PRUNING_BOUND + 4, header6, header6_hash, &authorities, max).is_ok());
}
}
Loading

0 comments on commit 541e9c8

Please sign in to comment.