mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 13:57:58 +00:00
Init store for slots-headers (#2492)
* 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
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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};
|
||||
@@ -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,
|
||||
@@ -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>,
|
||||
@@ -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>,
|
||||
@@ -459,7 +459,8 @@ 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,
|
||||
@@ -467,8 +468,9 @@ fn check_header<B: Block, P: Pair>(
|
||||
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,
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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>,
|
||||
@@ -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,
|
||||
@@ -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>,
|
||||
@@ -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>,
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
@@ -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,
|
||||
@@ -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(|| {
|
||||
@@ -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))
|
||||
@@ -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>,
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// 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/>.
|
||||
|
||||
//! Schema for slots in the aux-db.
|
||||
|
||||
use std::sync::Arc;
|
||||
use codec::{Encode, Decode};
|
||||
use client::backend::AuxStore;
|
||||
use client::error::{Result as ClientResult, Error as ClientError};
|
||||
use runtime_primitives::traits::Header;
|
||||
|
||||
const SLOT_HEADER_MAP_KEY: &[u8] = b"slot_header_map";
|
||||
const SLOT_HEADER_START: &[u8] = b"slot_header_start";
|
||||
|
||||
/// We keep at least this number of slots in database.
|
||||
pub const MAX_SLOT_CAPACITY: u64 = 1000;
|
||||
/// We prune slots when they reach this number.
|
||||
pub const PRUNING_BOUND: u64 = 2 * MAX_SLOT_CAPACITY;
|
||||
|
||||
fn load_decode<C, T>(backend: Arc<C>, key: &[u8]) -> ClientResult<Option<T>>
|
||||
where
|
||||
C: AuxStore,
|
||||
T: Decode,
|
||||
{
|
||||
match backend.get_aux(key)? {
|
||||
None => Ok(None),
|
||||
Some(t) => T::decode(&mut &t[..])
|
||||
.ok_or_else(
|
||||
|| ClientError::Backend(format!("Slots DB is corrupted.")).into(),
|
||||
)
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an equivocation proof.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EquivocationProof<H> {
|
||||
slot: u64,
|
||||
fst_header: H,
|
||||
snd_header: H,
|
||||
}
|
||||
|
||||
impl<H> EquivocationProof<H> {
|
||||
/// Get the slot number where the equivocation happened.
|
||||
pub fn slot(&self) -> u64 {
|
||||
self.slot
|
||||
}
|
||||
|
||||
/// Get the first header involved in the equivocation.
|
||||
pub fn fst_header(&self) -> &H {
|
||||
&self.fst_header
|
||||
}
|
||||
|
||||
/// Get the second header involved in the equivocation.
|
||||
pub fn snd_header(&self) -> &H {
|
||||
&self.snd_header
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the header is an equivocation and returns the proof in that case.
|
||||
///
|
||||
/// Note: it detects equivocations only when slot_now - slot <= MAX_SLOT_CAPACITY.
|
||||
pub fn check_equivocation<C, H, P>(
|
||||
backend: &Arc<C>,
|
||||
slot_now: u64,
|
||||
slot: u64,
|
||||
header: H,
|
||||
signer: P,
|
||||
) -> ClientResult<Option<EquivocationProof<H>>>
|
||||
where
|
||||
H: Header,
|
||||
C: AuxStore,
|
||||
P: Encode + Decode + PartialEq,
|
||||
{
|
||||
// We don't check equivocations for old headers out of our capacity.
|
||||
if slot_now - slot > MAX_SLOT_CAPACITY {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
// Key for this slot.
|
||||
let mut curr_slot_key = SLOT_HEADER_MAP_KEY.to_vec();
|
||||
slot.using_encoded(|s| curr_slot_key.extend(s));
|
||||
|
||||
// Get headers of this slot.
|
||||
let mut headers_with_sig = load_decode::<_, Vec<(H, P)>>(backend.clone(), &curr_slot_key[..])?
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
// Get first slot saved.
|
||||
let slot_header_start = SLOT_HEADER_START.to_vec();
|
||||
let first_saved_slot = load_decode::<_, u64>(backend.clone(), &slot_header_start[..])?
|
||||
.unwrap_or(slot);
|
||||
|
||||
for (prev_header, prev_signer) in headers_with_sig.iter() {
|
||||
// A proof of equivocation consists of two headers:
|
||||
// 1) signed by the same voter,
|
||||
if *prev_signer == signer {
|
||||
// 2) with different hash
|
||||
if header.hash() != prev_header.hash() {
|
||||
return Ok(Some(EquivocationProof {
|
||||
slot, // 3) and mentioning the same slot.
|
||||
fst_header: prev_header.clone(),
|
||||
snd_header: header.clone(),
|
||||
}));
|
||||
} else {
|
||||
// We don't need to continue in case of duplicated header,
|
||||
// since it's already saved and a possible equivocation
|
||||
// would have been detected before.
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut keys_to_delete = vec![];
|
||||
let mut new_first_saved_slot = first_saved_slot;
|
||||
|
||||
if slot_now - first_saved_slot >= PRUNING_BOUND {
|
||||
let prefix = SLOT_HEADER_MAP_KEY.to_vec();
|
||||
new_first_saved_slot = slot_now.saturating_sub(MAX_SLOT_CAPACITY);
|
||||
|
||||
for s in first_saved_slot..new_first_saved_slot {
|
||||
let mut p = prefix.clone();
|
||||
s.using_encoded(|s| p.extend(s));
|
||||
keys_to_delete.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
headers_with_sig.push((header, signer));
|
||||
|
||||
backend.insert_aux(
|
||||
&[
|
||||
(&curr_slot_key[..], headers_with_sig.encode().as_slice()),
|
||||
(&slot_header_start[..], new_first_saved_slot.encode().as_slice()),
|
||||
],
|
||||
&keys_to_delete.iter().map(|k| &k[..]).collect::<Vec<&[u8]>>()[..],
|
||||
)?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
@@ -23,8 +23,10 @@
|
||||
#![forbid(warnings, unsafe_code, missing_docs)]
|
||||
|
||||
mod slots;
|
||||
mod aux_schema;
|
||||
|
||||
pub use slots::{slot_now, SlotInfo, Slots};
|
||||
pub use aux_schema::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use consensus_common::{SyncOracle, SelectChain};
|
||||
|
||||
Reference in New Issue
Block a user