mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 18:41:03 +00:00
Adding Bridges code as git subtree. (#2515)
* Add instructions. * Squashed 'bridges/' content from commit 345e84a21 git-subtree-dir: bridges git-subtree-split: 345e84a2146b56628e9888c9f5e129cb40e868a9 * Remove bridges workspace file to avoid confusing Cargo. * Add some bridges primitives to Polkadot workspace. * Improve docs.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "pallet-bridge-eth-poa"
|
||||
description = "A Substrate Runtime module that is able to verify PoA headers and their finality."
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"], optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa", default-features = false }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false, optional = true }
|
||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
libsecp256k1 = { version = "0.3.4", features = ["hmac"] }
|
||||
hex-literal = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-eth-poa/std",
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"serde",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"libsecp256k1",
|
||||
]
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::{
|
||||
build_custom_header, build_genesis_header, insert_header, validator_utils::*, validators_change_receipt,
|
||||
HeaderBuilder,
|
||||
};
|
||||
|
||||
use bp_eth_poa::{compute_merkle_root, U256};
|
||||
use frame_benchmarking::benchmarks_instance;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
benchmarks_instance! {
|
||||
// Benchmark `import_unsigned_header` extrinsic with the best possible conditions:
|
||||
// * Parent header is finalized.
|
||||
// * New header doesn't require receipts.
|
||||
// * Nothing is finalized by new header.
|
||||
// * Nothing is pruned by new header.
|
||||
import_unsigned_header_best_case {
|
||||
let n in 1..1000;
|
||||
|
||||
let num_validators = 2;
|
||||
let initial_header = initialize_bench::<T, I>(num_validators);
|
||||
|
||||
// prepare header to be inserted
|
||||
let header = build_custom_header(
|
||||
&validator(1),
|
||||
&initial_header,
|
||||
|mut header| {
|
||||
header.gas_limit = header.gas_limit + U256::from(n);
|
||||
header
|
||||
},
|
||||
);
|
||||
}: import_unsigned_header(RawOrigin::None, header, None)
|
||||
verify {
|
||||
let storage = BridgeStorage::<T, I>::new();
|
||||
assert_eq!(storage.best_block().0.number, 1);
|
||||
assert_eq!(storage.finalized_block().number, 0);
|
||||
}
|
||||
|
||||
// Our goal with this bench is to try and see the effect that finalizing difference ranges of
|
||||
// blocks has on our import time. As such we need to make sure that we keep the number of
|
||||
// validators fixed while changing the number blocks finalized (the complexity parameter) by
|
||||
// importing the last header.
|
||||
//
|
||||
// One important thing to keep in mind is that the runtime provides a finality cache in order to
|
||||
// reduce the overhead of header finalization. However, this is only triggered every 16 blocks.
|
||||
import_unsigned_finality {
|
||||
// Our complexity parameter, n, will represent the number of blocks imported before
|
||||
// finalization.
|
||||
let n in 1..7;
|
||||
|
||||
let mut storage = BridgeStorage::<T, I>::new();
|
||||
let num_validators: u32 = 2;
|
||||
let initial_header = initialize_bench::<T, I>(num_validators as usize);
|
||||
|
||||
// Since we only have two validators we need to make sure the number of blocks is even to
|
||||
// make sure the right validator signs the final block
|
||||
let num_blocks = 2 * n;
|
||||
let mut headers = Vec::new();
|
||||
let mut parent = initial_header.clone();
|
||||
|
||||
// Import a bunch of headers without any verification, will ensure that they're not
|
||||
// finalized prematurely
|
||||
for i in 1..=num_blocks {
|
||||
let header = HeaderBuilder::with_parent(&parent).sign_by(&validator(0));
|
||||
let id = header.compute_id();
|
||||
insert_header(&mut storage, header.clone());
|
||||
headers.push(header.clone());
|
||||
parent = header;
|
||||
}
|
||||
|
||||
let last_header = headers.last().unwrap().clone();
|
||||
let last_authority = validator(1);
|
||||
|
||||
// Need to make sure that the header we're going to import hasn't been inserted
|
||||
// into storage already
|
||||
let header = HeaderBuilder::with_parent(&last_header).sign_by(&last_authority);
|
||||
}: import_unsigned_header(RawOrigin::None, header, None)
|
||||
verify {
|
||||
let storage = BridgeStorage::<T, I>::new();
|
||||
assert_eq!(storage.best_block().0.number, (num_blocks + 1) as u64);
|
||||
assert_eq!(storage.finalized_block().number, num_blocks as u64);
|
||||
}
|
||||
|
||||
// Basically the exact same as `import_unsigned_finality` but with a different range for the
|
||||
// complexity parameter. In this bench we use a larger range of blocks to see how performance
|
||||
// changes when the finality cache kicks in (>16 blocks).
|
||||
import_unsigned_finality_with_cache {
|
||||
// Our complexity parameter, n, will represent the number of blocks imported before
|
||||
// finalization.
|
||||
let n in 7..100;
|
||||
|
||||
let mut storage = BridgeStorage::<T, I>::new();
|
||||
let num_validators: u32 = 2;
|
||||
let initial_header = initialize_bench::<T, I>(num_validators as usize);
|
||||
|
||||
// Since we only have two validators we need to make sure the number of blocks is even to
|
||||
// make sure the right validator signs the final block
|
||||
let num_blocks = 2 * n;
|
||||
let mut headers = Vec::new();
|
||||
let mut parent = initial_header.clone();
|
||||
|
||||
// Import a bunch of headers without any verification, will ensure that they're not
|
||||
// finalized prematurely
|
||||
for i in 1..=num_blocks {
|
||||
let header = HeaderBuilder::with_parent(&parent).sign_by(&validator(0));
|
||||
let id = header.compute_id();
|
||||
insert_header(&mut storage, header.clone());
|
||||
headers.push(header.clone());
|
||||
parent = header;
|
||||
}
|
||||
|
||||
let last_header = headers.last().unwrap().clone();
|
||||
let last_authority = validator(1);
|
||||
|
||||
// Need to make sure that the header we're going to import hasn't been inserted
|
||||
// into storage already
|
||||
let header = HeaderBuilder::with_parent(&last_header).sign_by(&last_authority);
|
||||
}: import_unsigned_header(RawOrigin::None, header, None)
|
||||
verify {
|
||||
let storage = BridgeStorage::<T, I>::new();
|
||||
assert_eq!(storage.best_block().0.number, (num_blocks + 1) as u64);
|
||||
assert_eq!(storage.finalized_block().number, num_blocks as u64);
|
||||
}
|
||||
|
||||
// A block import may trigger a pruning event, which adds extra work to the import progress.
|
||||
// In this bench we trigger a pruning event in order to see how much extra time is spent by the
|
||||
// runtime dealing with it. In the Ethereum Pallet, we're limited pruning to eight blocks in a
|
||||
// single import, as dictated by MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT.
|
||||
import_unsigned_pruning {
|
||||
let n in 1..MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT as u32;
|
||||
|
||||
let mut storage = BridgeStorage::<T, I>::new();
|
||||
|
||||
let num_validators = 3;
|
||||
let initial_header = initialize_bench::<T, I>(num_validators as usize);
|
||||
let validators = validators(num_validators);
|
||||
|
||||
// Want to prune eligible blocks between [0, n)
|
||||
BlocksToPrune::<I>::put(PruningRange {
|
||||
oldest_unpruned_block: 0,
|
||||
oldest_block_to_keep: n as u64,
|
||||
});
|
||||
|
||||
let mut parent = initial_header;
|
||||
for i in 1..=n {
|
||||
let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
|
||||
let id = header.compute_id();
|
||||
insert_header(&mut storage, header.clone());
|
||||
parent = header;
|
||||
}
|
||||
|
||||
let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
|
||||
}: import_unsigned_header(RawOrigin::None, header, None)
|
||||
verify {
|
||||
let storage = BridgeStorage::<T, I>::new();
|
||||
let max_pruned: u64 = (n - 1) as _;
|
||||
assert_eq!(storage.best_block().0.number, (n + 1) as u64);
|
||||
assert!(HeadersByNumber::<I>::get(&0).is_none());
|
||||
assert!(HeadersByNumber::<I>::get(&max_pruned).is_none());
|
||||
}
|
||||
|
||||
// The goal of this bench is to import a block which contains a transaction receipt. The receipt
|
||||
// will contain a validator set change. Verifying the receipt root is an expensive operation to
|
||||
// do, which is why we're interested in benchmarking it.
|
||||
import_unsigned_with_receipts {
|
||||
let n in 1..100;
|
||||
|
||||
let mut storage = BridgeStorage::<T, I>::new();
|
||||
|
||||
let num_validators = 1;
|
||||
let initial_header = initialize_bench::<T, I>(num_validators as usize);
|
||||
|
||||
let mut receipts = vec![];
|
||||
for i in 1..=n {
|
||||
let receipt = validators_change_receipt(Default::default());
|
||||
receipts.push(receipt)
|
||||
}
|
||||
let encoded_receipts = receipts.iter().map(|r| r.rlp());
|
||||
|
||||
// We need this extra header since this is what signals a validator set transition. This
|
||||
// will ensure that the next header is within the "Contract" window
|
||||
let header1 = HeaderBuilder::with_parent(&initial_header).sign_by(&validator(0));
|
||||
insert_header(&mut storage, header1.clone());
|
||||
|
||||
let header = build_custom_header(
|
||||
&validator(0),
|
||||
&header1,
|
||||
|mut header| {
|
||||
// Logs Bloom signals a change in validator set
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
header.receipts_root = compute_merkle_root(encoded_receipts);
|
||||
header
|
||||
},
|
||||
);
|
||||
}: import_unsigned_header(RawOrigin::None, header, Some(receipts))
|
||||
verify {
|
||||
let storage = BridgeStorage::<T, I>::new();
|
||||
assert_eq!(storage.best_block().0.number, 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_bench<T: Config<I>, I: Instance>(num_validators: usize) -> AuraHeader {
|
||||
// Initialize storage with some initial header
|
||||
let initial_header = build_genesis_header(&validator(0));
|
||||
let initial_difficulty = initial_header.difficulty;
|
||||
let initial_validators = validators_addresses(num_validators as usize);
|
||||
|
||||
initialize_storage::<T, I>(&initial_header, initial_difficulty, &initial_validators);
|
||||
|
||||
initial_header
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, TestRuntime};
|
||||
use frame_support::assert_ok;
|
||||
|
||||
#[test]
|
||||
fn insert_unsigned_header_best_case() {
|
||||
run_test(1, |_| {
|
||||
assert_ok!(test_benchmark_import_unsigned_header_best_case::<TestRuntime>());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_unsigned_header_finality() {
|
||||
run_test(1, |_| {
|
||||
assert_ok!(test_benchmark_import_unsigned_finality::<TestRuntime>());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_unsigned_header_finality_with_cache() {
|
||||
run_test(1, |_| {
|
||||
assert_ok!(test_benchmark_import_unsigned_finality_with_cache::<TestRuntime>());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_unsigned_header_pruning() {
|
||||
run_test(1, |_| {
|
||||
assert_ok!(test_benchmark_import_unsigned_pruning::<TestRuntime>());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_unsigned_header_receipts() {
|
||||
run_test(1, |_| {
|
||||
assert_ok!(test_benchmark_import_unsigned_with_receipts::<TestRuntime>());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
/// Header import error.
|
||||
#[derive(Clone, Copy, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum Error {
|
||||
/// The header is beyond last finalized and can not be imported.
|
||||
AncientHeader = 0,
|
||||
/// The header is already imported.
|
||||
KnownHeader = 1,
|
||||
/// Seal has an incorrect format.
|
||||
InvalidSealArity = 2,
|
||||
/// Block number isn't sensible.
|
||||
RidiculousNumber = 3,
|
||||
/// Block has too much gas used.
|
||||
TooMuchGasUsed = 4,
|
||||
/// Gas limit header field is invalid.
|
||||
InvalidGasLimit = 5,
|
||||
/// Extra data is of an invalid length.
|
||||
ExtraDataOutOfBounds = 6,
|
||||
/// Timestamp header overflowed.
|
||||
TimestampOverflow = 7,
|
||||
/// The parent header is missing from the blockchain.
|
||||
MissingParentBlock = 8,
|
||||
/// The header step is missing from the header.
|
||||
MissingStep = 9,
|
||||
/// The header signature is missing from the header.
|
||||
MissingSignature = 10,
|
||||
/// Empty steps are missing from the header.
|
||||
MissingEmptySteps = 11,
|
||||
/// The same author issued different votes at the same step.
|
||||
DoubleVote = 12,
|
||||
/// Validation proof insufficient.
|
||||
InsufficientProof = 13,
|
||||
/// Difficulty header field is invalid.
|
||||
InvalidDifficulty = 14,
|
||||
/// The received block is from an incorrect proposer.
|
||||
NotValidator = 15,
|
||||
/// Missing transaction receipts for the operation.
|
||||
MissingTransactionsReceipts = 16,
|
||||
/// Redundant transaction receipts are provided.
|
||||
RedundantTransactionsReceipts = 17,
|
||||
/// Provided transactions receipts are not matching the header.
|
||||
TransactionsReceiptsMismatch = 18,
|
||||
/// Can't accept unsigned header from the far future.
|
||||
UnsignedTooFarInTheFuture = 19,
|
||||
/// Trying to finalize sibling of finalized block.
|
||||
TryingToFinalizeSibling = 20,
|
||||
/// Header timestamp is ahead of on-chain timestamp
|
||||
HeaderTimestampIsAhead = 21,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn msg(&self) -> &'static str {
|
||||
match *self {
|
||||
Error::AncientHeader => "Header is beyound last finalized and can not be imported",
|
||||
Error::KnownHeader => "Header is already imported",
|
||||
Error::InvalidSealArity => "Header has an incorrect seal",
|
||||
Error::RidiculousNumber => "Header has too large number",
|
||||
Error::TooMuchGasUsed => "Header has too much gas used",
|
||||
Error::InvalidGasLimit => "Header has invalid gas limit",
|
||||
Error::ExtraDataOutOfBounds => "Header has too large extra data",
|
||||
Error::TimestampOverflow => "Header has too large timestamp",
|
||||
Error::MissingParentBlock => "Header has unknown parent hash",
|
||||
Error::MissingStep => "Header is missing step seal",
|
||||
Error::MissingSignature => "Header is missing signature seal",
|
||||
Error::MissingEmptySteps => "Header is missing empty steps seal",
|
||||
Error::DoubleVote => "Header has invalid step in seal",
|
||||
Error::InsufficientProof => "Header has insufficient proof",
|
||||
Error::InvalidDifficulty => "Header has invalid difficulty",
|
||||
Error::NotValidator => "Header is sealed by unexpected validator",
|
||||
Error::MissingTransactionsReceipts => "The import operation requires transactions receipts",
|
||||
Error::RedundantTransactionsReceipts => "Redundant transactions receipts are provided",
|
||||
Error::TransactionsReceiptsMismatch => "Invalid transactions receipts provided",
|
||||
Error::UnsignedTooFarInTheFuture => "The unsigned header is too far in future",
|
||||
Error::TryingToFinalizeSibling => "Trying to finalize sibling of finalized block",
|
||||
Error::HeaderTimestampIsAhead => "Header timestamp is ahead of on-chain timestamp",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return unique error code.
|
||||
pub fn code(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::Storage;
|
||||
use bp_eth_poa::{public_to_address, Address, AuraHeader, HeaderId, SealedEmptyStep, H256};
|
||||
use codec::{Decode, Encode};
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::collections::{
|
||||
btree_map::{BTreeMap, Entry},
|
||||
btree_set::BTreeSet,
|
||||
vec_deque::VecDeque,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Cached finality votes for given block.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct CachedFinalityVotes<Submitter> {
|
||||
/// True if we have stopped at best finalized block' sibling. This means
|
||||
/// that we are trying to finalize block from fork that has forked before
|
||||
/// best finalized.
|
||||
pub stopped_at_finalized_sibling: bool,
|
||||
/// Header ancestors that were read while we have been searching for
|
||||
/// cached votes entry. Newest header has index 0.
|
||||
pub unaccounted_ancestry: VecDeque<(HeaderId, Option<Submitter>, AuraHeader)>,
|
||||
/// Cached finality votes, if they have been found. The associated
|
||||
/// header is not included into `unaccounted_ancestry`.
|
||||
pub votes: Option<FinalityVotes<Submitter>>,
|
||||
}
|
||||
|
||||
/// Finality effects.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct FinalityEffects<Submitter> {
|
||||
/// Finalized headers.
|
||||
pub finalized_headers: Vec<(HeaderId, Option<Submitter>)>,
|
||||
/// Finality votes used in computation.
|
||||
pub votes: FinalityVotes<Submitter>,
|
||||
}
|
||||
|
||||
/// Finality votes for given block.
|
||||
#[derive(RuntimeDebug, Decode, Encode)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub struct FinalityVotes<Submitter> {
|
||||
/// Number of votes per each validator.
|
||||
pub votes: BTreeMap<Address, u64>,
|
||||
/// Ancestry blocks with oldest ancestors at the beginning and newest at the
|
||||
/// end of the queue.
|
||||
pub ancestry: VecDeque<FinalityAncestor<Submitter>>,
|
||||
}
|
||||
|
||||
/// Information about block ancestor that is used in computations.
|
||||
#[derive(RuntimeDebug, Decode, Encode)]
|
||||
#[cfg_attr(test, derive(Clone, Default, PartialEq))]
|
||||
pub struct FinalityAncestor<Submitter> {
|
||||
/// Bock id.
|
||||
pub id: HeaderId,
|
||||
/// Block submitter.
|
||||
pub submitter: Option<Submitter>,
|
||||
/// Validators that have signed this block and empty steps on top
|
||||
/// of this block.
|
||||
pub signers: BTreeSet<Address>,
|
||||
}
|
||||
|
||||
/// Tries to finalize blocks when given block is imported.
|
||||
///
|
||||
/// Returns numbers and hashes of finalized blocks in ascending order.
|
||||
pub fn finalize_blocks<S: Storage>(
|
||||
storage: &S,
|
||||
best_finalized: HeaderId,
|
||||
header_validators: (HeaderId, &[Address]),
|
||||
id: HeaderId,
|
||||
submitter: Option<&S::Submitter>,
|
||||
header: &AuraHeader,
|
||||
two_thirds_majority_transition: u64,
|
||||
) -> Result<FinalityEffects<S::Submitter>, Error> {
|
||||
// compute count of voters for every unfinalized block in ancestry
|
||||
let validators = header_validators.1.iter().collect();
|
||||
let votes = prepare_votes(
|
||||
header
|
||||
.parent_id()
|
||||
.map(|parent_id| {
|
||||
storage.cached_finality_votes(&parent_id, &best_finalized, |hash| {
|
||||
*hash == header_validators.0.hash || *hash == best_finalized.hash
|
||||
})
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
best_finalized,
|
||||
&validators,
|
||||
id,
|
||||
header,
|
||||
submitter.cloned(),
|
||||
)?;
|
||||
|
||||
// now let's iterate in reverse order && find just finalized blocks
|
||||
let mut finalized_headers = Vec::new();
|
||||
let mut current_votes = votes.votes.clone();
|
||||
for ancestor in &votes.ancestry {
|
||||
if !is_finalized(
|
||||
&validators,
|
||||
¤t_votes,
|
||||
ancestor.id.number >= two_thirds_majority_transition,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
remove_signers_votes(&ancestor.signers, &mut current_votes);
|
||||
finalized_headers.push((ancestor.id, ancestor.submitter.clone()));
|
||||
}
|
||||
|
||||
Ok(FinalityEffects {
|
||||
finalized_headers,
|
||||
votes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if there are enough votes to treat this header as finalized.
|
||||
fn is_finalized(
|
||||
validators: &BTreeSet<&Address>,
|
||||
votes: &BTreeMap<Address, u64>,
|
||||
requires_two_thirds_majority: bool,
|
||||
) -> bool {
|
||||
(!requires_two_thirds_majority && votes.len() * 2 > validators.len())
|
||||
|| (requires_two_thirds_majority && votes.len() * 3 > validators.len() * 2)
|
||||
}
|
||||
|
||||
/// Prepare 'votes' of header and its ancestors' signers.
|
||||
pub(crate) fn prepare_votes<Submitter>(
|
||||
mut cached_votes: CachedFinalityVotes<Submitter>,
|
||||
best_finalized: HeaderId,
|
||||
validators: &BTreeSet<&Address>,
|
||||
id: HeaderId,
|
||||
header: &AuraHeader,
|
||||
submitter: Option<Submitter>,
|
||||
) -> Result<FinalityVotes<Submitter>, Error> {
|
||||
// if we have reached finalized block sibling, then we're trying
|
||||
// to switch finalized blocks
|
||||
if cached_votes.stopped_at_finalized_sibling {
|
||||
return Err(Error::TryingToFinalizeSibling);
|
||||
}
|
||||
|
||||
// this fn can only work with single validators set
|
||||
if !validators.contains(&header.author) {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
// now we have votes that were valid when some block B has been inserted
|
||||
// things may have changed a bit, but we do not need to read anything else
|
||||
// from the db, because we have ancestry
|
||||
// so the only thing we need to do is:
|
||||
// 1) remove votes from blocks that have been finalized after B has been inserted;
|
||||
// 2) add votes from B descendants
|
||||
let mut votes = cached_votes.votes.unwrap_or_default();
|
||||
|
||||
// remove votes from finalized blocks
|
||||
while let Some(old_ancestor) = votes.ancestry.pop_front() {
|
||||
if old_ancestor.id.number > best_finalized.number {
|
||||
votes.ancestry.push_front(old_ancestor);
|
||||
break;
|
||||
}
|
||||
|
||||
remove_signers_votes(&old_ancestor.signers, &mut votes.votes);
|
||||
}
|
||||
|
||||
// add votes from new blocks
|
||||
let mut parent_empty_step_signers = empty_steps_signers(header);
|
||||
let mut unaccounted_ancestry = VecDeque::new();
|
||||
while let Some((ancestor_id, ancestor_submitter, ancestor)) = cached_votes.unaccounted_ancestry.pop_front() {
|
||||
let mut signers = empty_steps_signers(&ancestor);
|
||||
sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers);
|
||||
signers.insert(ancestor.author);
|
||||
|
||||
add_signers_votes(validators, &signers, &mut votes.votes)?;
|
||||
|
||||
unaccounted_ancestry.push_front(FinalityAncestor {
|
||||
id: ancestor_id,
|
||||
submitter: ancestor_submitter,
|
||||
signers,
|
||||
});
|
||||
}
|
||||
votes.ancestry.extend(unaccounted_ancestry);
|
||||
|
||||
// add votes from block itself
|
||||
let mut header_signers = BTreeSet::new();
|
||||
header_signers.insert(header.author);
|
||||
*votes.votes.entry(header.author).or_insert(0) += 1;
|
||||
votes.ancestry.push_back(FinalityAncestor {
|
||||
id,
|
||||
submitter,
|
||||
signers: header_signers,
|
||||
});
|
||||
|
||||
Ok(votes)
|
||||
}
|
||||
|
||||
/// Increase count of 'votes' for every passed signer.
|
||||
/// Fails if at least one of signers is not in the `validators` set.
|
||||
fn add_signers_votes(
|
||||
validators: &BTreeSet<&Address>,
|
||||
signers_to_add: &BTreeSet<Address>,
|
||||
votes: &mut BTreeMap<Address, u64>,
|
||||
) -> Result<(), Error> {
|
||||
for signer in signers_to_add {
|
||||
if !validators.contains(signer) {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
*votes.entry(*signer).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrease 'votes' count for every passed signer.
|
||||
fn remove_signers_votes(signers_to_remove: &BTreeSet<Address>, votes: &mut BTreeMap<Address, u64>) {
|
||||
for signer in signers_to_remove {
|
||||
match votes.entry(*signer) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
if *entry.get() <= 1 {
|
||||
entry.remove();
|
||||
} else {
|
||||
*entry.get_mut() -= 1;
|
||||
}
|
||||
}
|
||||
Entry::Vacant(_) => unreachable!("we only remove signers that have been added; qed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns unique set of empty steps signers.
|
||||
fn empty_steps_signers(header: &AuraHeader) -> BTreeSet<Address> {
|
||||
header
|
||||
.empty_steps()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|step| empty_step_signer(&step, &header.parent_hash))
|
||||
.collect::<BTreeSet<_>>()
|
||||
}
|
||||
|
||||
/// Returns author of empty step signature.
|
||||
fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option<Address> {
|
||||
let message = empty_step.message(parent_hash);
|
||||
secp256k1_ecdsa_recover(empty_step.signature.as_fixed_bytes(), message.as_fixed_bytes())
|
||||
.ok()
|
||||
.map(|public| public_to_address(&public))
|
||||
}
|
||||
|
||||
impl<Submitter> Default for CachedFinalityVotes<Submitter> {
|
||||
fn default() -> Self {
|
||||
CachedFinalityVotes {
|
||||
stopped_at_finalized_sibling: false,
|
||||
unaccounted_ancestry: VecDeque::new(),
|
||||
votes: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Submitter> Default for FinalityVotes<Submitter> {
|
||||
fn default() -> Self {
|
||||
FinalityVotes {
|
||||
votes: BTreeMap::new(),
|
||||
ancestry: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{insert_header, run_test, validator, validators_addresses, HeaderBuilder, TestRuntime};
|
||||
use crate::{BridgeStorage, FinalityCache, HeaderToImport};
|
||||
use frame_support::StorageMap;
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 5;
|
||||
|
||||
#[test]
|
||||
fn verifies_header_author() {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&BridgeStorage::<TestRuntime>::new(),
|
||||
Default::default(),
|
||||
(Default::default(), &[]),
|
||||
Default::default(),
|
||||
None,
|
||||
&AuraHeader::default(),
|
||||
0,
|
||||
),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_blocks_works() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
// let's say we have 5 validators (we need 'votes' from 3 validators to achieve
|
||||
// finality)
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// when header#1 is inserted, nothing is finalized (1 vote)
|
||||
let header1 = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(0));
|
||||
let id1 = header1.compute_id();
|
||||
let mut header_to_import = HeaderToImport {
|
||||
context: storage.import_context(None, &header1.parent_hash).unwrap(),
|
||||
is_best: true,
|
||||
id: id1,
|
||||
header: header1,
|
||||
total_difficulty: 0.into(),
|
||||
enacted_change: None,
|
||||
scheduled_change: None,
|
||||
finality_votes: Default::default(),
|
||||
};
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id1,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
)
|
||||
.map(|eff| eff.finalized_headers),
|
||||
Ok(Vec::new()),
|
||||
);
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#2 is inserted, nothing is finalized (2 votes)
|
||||
header_to_import.header = HeaderBuilder::with_parent_hash(id1.hash).sign_by(&validator(1));
|
||||
header_to_import.id = header_to_import.header.compute_id();
|
||||
let id2 = header_to_import.header.compute_id();
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id2,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
)
|
||||
.map(|eff| eff.finalized_headers),
|
||||
Ok(Vec::new()),
|
||||
);
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#3 is inserted, header#1 is finalized (3 votes)
|
||||
header_to_import.header = HeaderBuilder::with_parent_hash(id2.hash).sign_by(&validator(2));
|
||||
header_to_import.id = header_to_import.header.compute_id();
|
||||
let id3 = header_to_import.header.compute_id();
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id3,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
)
|
||||
.map(|eff| eff.finalized_headers),
|
||||
Ok(vec![(id1, None)]),
|
||||
);
|
||||
storage.insert_header(header_to_import);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_votes_are_updated_with_ancestry() {
|
||||
// we're inserting header#5
|
||||
// cached votes are from header#3
|
||||
// header#4 has finalized header#1 and header#2
|
||||
// => when inserting header#5, we need to:
|
||||
// 1) remove votes from header#1 and header#2
|
||||
// 2) add votes from header#4 and header#5
|
||||
let validators = validators_addresses(5);
|
||||
let headers = (1..6)
|
||||
.map(|number| HeaderBuilder::with_number(number).sign_by(&validator(number as usize - 1)))
|
||||
.collect::<Vec<_>>();
|
||||
let ancestry = headers
|
||||
.iter()
|
||||
.map(|header| FinalityAncestor {
|
||||
id: header.compute_id(),
|
||||
signers: vec![header.author].into_iter().collect(),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let header5 = headers[4].clone();
|
||||
assert_eq!(
|
||||
prepare_votes::<()>(
|
||||
CachedFinalityVotes {
|
||||
stopped_at_finalized_sibling: false,
|
||||
unaccounted_ancestry: vec![(headers[3].compute_id(), None, headers[3].clone()),]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
votes: Some(FinalityVotes {
|
||||
votes: vec![(validators[0], 1), (validators[1], 1), (validators[2], 1),]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
ancestry: ancestry[..3].iter().cloned().collect(),
|
||||
}),
|
||||
},
|
||||
headers[1].compute_id(),
|
||||
&validators.iter().collect(),
|
||||
header5.compute_id(),
|
||||
&header5,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
FinalityVotes {
|
||||
votes: vec![(validators[2], 1), (validators[3], 1), (validators[4], 1),]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
ancestry: ancestry[2..].iter().cloned().collect(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_votes_respects_finality_cache() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
// we need signatures of 3 validators to finalize block
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// headers 1..3 are signed by validator#0
|
||||
// headers 4..6 are signed by validator#1
|
||||
// headers 7..9 are signed by validator#2
|
||||
let mut hashes = Vec::new();
|
||||
let mut headers = Vec::new();
|
||||
let mut ancestry = Vec::new();
|
||||
let mut parent_hash = ctx.genesis.compute_hash();
|
||||
for i in 1..10 {
|
||||
let header = HeaderBuilder::with_parent_hash(parent_hash).sign_by(&validator((i - 1) / 3));
|
||||
let id = header.compute_id();
|
||||
insert_header(&mut storage, header.clone());
|
||||
hashes.push(id.hash);
|
||||
ancestry.push(FinalityAncestor {
|
||||
id: header.compute_id(),
|
||||
submitter: None,
|
||||
signers: vec![header.author].into_iter().collect(),
|
||||
});
|
||||
headers.push(header);
|
||||
parent_hash = id.hash;
|
||||
}
|
||||
|
||||
// when we're inserting header#7 and last finalized header is 0:
|
||||
// check that votes at #7 are computed correctly without cache
|
||||
let expected_votes_at_7 = FinalityVotes {
|
||||
votes: vec![(ctx.addresses[0], 3), (ctx.addresses[1], 3), (ctx.addresses[2], 1)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
ancestry: ancestry[..7].iter().cloned().collect(),
|
||||
};
|
||||
let id7 = headers[6].compute_id();
|
||||
assert_eq!(
|
||||
prepare_votes(
|
||||
storage.cached_finality_votes(
|
||||
&headers.get(5).unwrap().compute_id(),
|
||||
&ctx.genesis.compute_id(),
|
||||
|_| false,
|
||||
),
|
||||
Default::default(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
expected_votes_at_7,
|
||||
);
|
||||
|
||||
// cached votes at #5
|
||||
let expected_votes_at_5 = FinalityVotes {
|
||||
votes: vec![(ctx.addresses[0], 3), (ctx.addresses[1], 2)].into_iter().collect(),
|
||||
ancestry: ancestry[..5].iter().cloned().collect(),
|
||||
};
|
||||
FinalityCache::<TestRuntime>::insert(hashes[4], expected_votes_at_5);
|
||||
|
||||
// when we're inserting header#7 and last finalized header is 0:
|
||||
// check that votes at #7 are computed correctly with cache
|
||||
assert_eq!(
|
||||
prepare_votes(
|
||||
storage.cached_finality_votes(
|
||||
&headers.get(5).unwrap().compute_id(),
|
||||
&ctx.genesis.compute_id(),
|
||||
|_| false,
|
||||
),
|
||||
Default::default(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
expected_votes_at_7,
|
||||
);
|
||||
|
||||
// when we're inserting header#7 and last finalized header is 3:
|
||||
// check that votes at #7 are computed correctly with cache
|
||||
let expected_votes_at_7 = FinalityVotes {
|
||||
votes: vec![(ctx.addresses[1], 3), (ctx.addresses[2], 1)].into_iter().collect(),
|
||||
ancestry: ancestry[3..7].iter().cloned().collect(),
|
||||
};
|
||||
assert_eq!(
|
||||
prepare_votes(
|
||||
storage.cached_finality_votes(
|
||||
&headers.get(5).unwrap().compute_id(),
|
||||
&headers.get(2).unwrap().compute_id(),
|
||||
|hash| *hash == hashes[2],
|
||||
),
|
||||
headers[2].compute_id(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
expected_votes_at_7,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepare_votes_fails_when_finalized_sibling_is_in_ancestry() {
|
||||
assert_eq!(
|
||||
prepare_votes::<()>(
|
||||
CachedFinalityVotes {
|
||||
stopped_at_finalized_sibling: true,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
&validators_addresses(3).iter().collect(),
|
||||
Default::default(),
|
||||
&Default::default(),
|
||||
None,
|
||||
),
|
||||
Err(Error::TryingToFinalizeSibling),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::finality::finalize_blocks;
|
||||
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||
use crate::verification::{is_importable_header, verify_aura_header};
|
||||
use crate::{AuraConfiguration, ChainTime, ChangeToEnact, PruningStrategy, Storage};
|
||||
use bp_eth_poa::{AuraHeader, HeaderId, Receipt};
|
||||
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
||||
|
||||
/// Imports bunch of headers and updates blocks finality.
|
||||
///
|
||||
/// Transactions receipts must be provided if `header_import_requires_receipts()`
|
||||
/// has returned true.
|
||||
/// If successful, returns tuple where first element is the number of useful headers
|
||||
/// we have imported and the second element is the number of useless headers (duplicate)
|
||||
/// we have NOT imported.
|
||||
/// Returns error if fatal error has occured during import. Some valid headers may be
|
||||
/// imported in this case.
|
||||
/// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/415)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn import_headers<S: Storage, PS: PruningStrategy, CT: ChainTime>(
|
||||
storage: &mut S,
|
||||
pruning_strategy: &mut PS,
|
||||
aura_config: &AuraConfiguration,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
submitter: Option<S::Submitter>,
|
||||
headers: Vec<(AuraHeader, Option<Vec<Receipt>>)>,
|
||||
chain_time: &CT,
|
||||
finalized_headers: &mut BTreeMap<S::Submitter, u64>,
|
||||
) -> Result<(u64, u64), Error> {
|
||||
let mut useful = 0;
|
||||
let mut useless = 0;
|
||||
for (header, receipts) in headers {
|
||||
let import_result = import_header(
|
||||
storage,
|
||||
pruning_strategy,
|
||||
aura_config,
|
||||
validators_config,
|
||||
submitter.clone(),
|
||||
header,
|
||||
chain_time,
|
||||
receipts,
|
||||
);
|
||||
|
||||
match import_result {
|
||||
Ok((_, finalized)) => {
|
||||
for (_, submitter) in finalized {
|
||||
if let Some(submitter) = submitter {
|
||||
*finalized_headers.entry(submitter).or_default() += 1;
|
||||
}
|
||||
}
|
||||
useful += 1;
|
||||
}
|
||||
Err(Error::AncientHeader) | Err(Error::KnownHeader) => useless += 1,
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((useful, useless))
|
||||
}
|
||||
|
||||
/// A vector of finalized headers and their submitters.
|
||||
pub type FinalizedHeaders<S> = Vec<(HeaderId, Option<<S as Storage>::Submitter>)>;
|
||||
|
||||
/// Imports given header and updates blocks finality (if required).
|
||||
///
|
||||
/// Transactions receipts must be provided if `header_import_requires_receipts()`
|
||||
/// has returned true.
|
||||
///
|
||||
/// Returns imported block id and list of all finalized headers.
|
||||
/// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/415)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn import_header<S: Storage, PS: PruningStrategy, CT: ChainTime>(
|
||||
storage: &mut S,
|
||||
pruning_strategy: &mut PS,
|
||||
aura_config: &AuraConfiguration,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
submitter: Option<S::Submitter>,
|
||||
header: AuraHeader,
|
||||
chain_time: &CT,
|
||||
receipts: Option<Vec<Receipt>>,
|
||||
) -> Result<(HeaderId, FinalizedHeaders<S>), Error> {
|
||||
// first check that we are able to import this header at all
|
||||
let (header_id, finalized_id) = is_importable_header(storage, &header)?;
|
||||
|
||||
// verify header
|
||||
let import_context = verify_aura_header(storage, aura_config, submitter, &header, chain_time)?;
|
||||
|
||||
// check if block schedules new validators
|
||||
let validators = Validators::new(validators_config);
|
||||
let (scheduled_change, enacted_change) = validators.extract_validators_change(&header, receipts)?;
|
||||
|
||||
// check if block finalizes some other blocks and corresponding scheduled validators
|
||||
let validators_set = import_context.validators_set();
|
||||
let finalized_blocks = finalize_blocks(
|
||||
storage,
|
||||
finalized_id,
|
||||
(validators_set.enact_block, &validators_set.validators),
|
||||
header_id,
|
||||
import_context.submitter(),
|
||||
&header,
|
||||
aura_config.two_thirds_majority_transition,
|
||||
)?;
|
||||
let enacted_change = enacted_change
|
||||
.map(|validators| ChangeToEnact {
|
||||
signal_block: None,
|
||||
validators,
|
||||
})
|
||||
.or_else(|| validators.finalize_validators_change(storage, &finalized_blocks.finalized_headers));
|
||||
|
||||
// NOTE: we can't return Err() from anywhere below this line
|
||||
// (because otherwise we'll have inconsistent storage if transaction will fail)
|
||||
|
||||
// and finally insert the block
|
||||
let (best_id, best_total_difficulty) = storage.best_block();
|
||||
let total_difficulty = import_context.total_difficulty() + header.difficulty;
|
||||
let is_best = total_difficulty > best_total_difficulty;
|
||||
storage.insert_header(import_context.into_import_header(
|
||||
is_best,
|
||||
header_id,
|
||||
header,
|
||||
total_difficulty,
|
||||
enacted_change,
|
||||
scheduled_change,
|
||||
finalized_blocks.votes,
|
||||
));
|
||||
|
||||
// compute upper border of updated pruning range
|
||||
let new_best_block_id = if is_best { header_id } else { best_id };
|
||||
let new_best_finalized_block_id = finalized_blocks.finalized_headers.last().map(|(id, _)| *id);
|
||||
let pruning_upper_bound = pruning_strategy.pruning_upper_bound(
|
||||
new_best_block_id.number,
|
||||
new_best_finalized_block_id
|
||||
.map(|id| id.number)
|
||||
.unwrap_or(finalized_id.number),
|
||||
);
|
||||
|
||||
// now mark finalized headers && prune old headers
|
||||
storage.finalize_and_prune_headers(new_best_finalized_block_id, pruning_upper_bound);
|
||||
|
||||
Ok((header_id, finalized_blocks.finalized_headers))
|
||||
}
|
||||
|
||||
/// Returns true if transactions receipts are required to import given header.
|
||||
pub fn header_import_requires_receipts<S: Storage>(
|
||||
storage: &S,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
header: &AuraHeader,
|
||||
) -> bool {
|
||||
is_importable_header(storage, header)
|
||||
.map(|_| Validators::new(validators_config))
|
||||
.map(|validators| validators.maybe_signals_validators_change(header))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
run_test, secret_to_address, test_aura_config, test_validators_config, validator, validators_addresses,
|
||||
validators_change_receipt, HeaderBuilder, KeepSomeHeadersBehindBest, TestRuntime, GAS_LIMIT,
|
||||
};
|
||||
use crate::validators::ValidatorsSource;
|
||||
use crate::DefaultInstance;
|
||||
use crate::{BlocksToPrune, BridgeStorage, Headers, PruningRange};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use secp256k1::SecretKey;
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
#[test]
|
||||
fn rejects_finalized_block_competitors() {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
storage.finalize_and_prune_headers(
|
||||
Some(HeaderId {
|
||||
number: 100,
|
||||
..Default::default()
|
||||
}),
|
||||
0,
|
||||
);
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&test_validators_config(),
|
||||
None,
|
||||
Default::default(),
|
||||
&(),
|
||||
None,
|
||||
),
|
||||
Err(Error::AncientHeader),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_known_header() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&test_validators_config(),
|
||||
None,
|
||||
header.clone(),
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.map(|_| ()),
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&test_validators_config(),
|
||||
None,
|
||||
header,
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.map(|_| ()),
|
||||
Err(Error::KnownHeader),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_header_works() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let validators_config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(ctx.addresses.clone())),
|
||||
(1, ValidatorsSource::List(validators_addresses(2))),
|
||||
]);
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
|
||||
let hash = header.compute_hash();
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
None,
|
||||
header,
|
||||
&(),
|
||||
None
|
||||
)
|
||||
.map(|_| ()),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// check that new validators will be used for next header
|
||||
let imported_header = Headers::<TestRuntime>::get(&hash).unwrap();
|
||||
assert_eq!(
|
||||
imported_header.next_validators_set_id,
|
||||
1, // new set is enacted from config
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_are_pruned_during_import() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let validators_config =
|
||||
ValidatorsConfiguration::Single(ValidatorsSource::Contract([3; 20].into(), ctx.addresses.clone()));
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// header [0..11] are finalizing blocks [0; 9]
|
||||
// => since we want to keep 10 finalized blocks, we aren't pruning anything
|
||||
let mut latest_block_id = Default::default();
|
||||
for i in 1..11 {
|
||||
let header = HeaderBuilder::with_parent_number(i - 1).sign_by_set(&validators);
|
||||
let parent_id = header.parent_id().unwrap();
|
||||
|
||||
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
Some(100),
|
||||
header,
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
match i {
|
||||
2..=10 => assert_eq!(finalized_blocks, vec![(parent_id, Some(100))], "At {}", i,),
|
||||
_ => assert_eq!(finalized_blocks, vec![], "At {}", i),
|
||||
}
|
||||
latest_block_id = rolling_last_block_id;
|
||||
}
|
||||
assert!(storage.header(&ctx.genesis.compute_hash()).is_some());
|
||||
|
||||
// header 11 finalizes headers [10] AND schedules change
|
||||
// => we prune header#0
|
||||
let header11 = HeaderBuilder::with_parent_number(10)
|
||||
.log_bloom((&[0xff; 256]).into())
|
||||
.receipts_root(
|
||||
"ead6c772ba0083bbff497ba0f4efe47c199a2655401096c21ab7450b6c466d97"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.sign_by_set(&validators);
|
||||
let parent_id = header11.parent_id().unwrap();
|
||||
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
Some(101),
|
||||
header11.clone(),
|
||||
&(),
|
||||
Some(vec![validators_change_receipt(latest_block_id.hash)]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(finalized_blocks, vec![(parent_id, Some(100))],);
|
||||
assert!(storage.header(&ctx.genesis.compute_hash()).is_none());
|
||||
latest_block_id = rolling_last_block_id;
|
||||
|
||||
// and now let's say validators 1 && 2 went offline
|
||||
// => in the range 12-25 no blocks are finalized, but we still continue to prune old headers
|
||||
// until header#11 is met. we can't prune #11, because it schedules change
|
||||
let mut step = 56u64;
|
||||
let mut expected_blocks = vec![(header11.compute_id(), Some(101))];
|
||||
for i in 12..25 {
|
||||
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
|
||||
.difficulty(i.into())
|
||||
.step(step)
|
||||
.sign_by_set(&validators);
|
||||
expected_blocks.push((header.compute_id(), Some(102)));
|
||||
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
Some(102),
|
||||
header,
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(finalized_blocks, vec![],);
|
||||
latest_block_id = rolling_last_block_id;
|
||||
step += 3;
|
||||
}
|
||||
assert_eq!(
|
||||
BlocksToPrune::<DefaultInstance>::get(),
|
||||
PruningRange {
|
||||
oldest_unpruned_block: 11,
|
||||
oldest_block_to_keep: 14,
|
||||
},
|
||||
);
|
||||
|
||||
// now let's insert block signed by validator 1
|
||||
// => blocks 11..24 are finalized and blocks 11..14 are pruned
|
||||
step -= 2;
|
||||
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
|
||||
.difficulty(25.into())
|
||||
.step(step)
|
||||
.sign_by_set(&validators);
|
||||
let (_, finalized_blocks) = import_header(
|
||||
&mut storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
Some(103),
|
||||
header,
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(finalized_blocks, expected_blocks);
|
||||
assert_eq!(
|
||||
BlocksToPrune::<DefaultInstance>::get(),
|
||||
PruningRange {
|
||||
oldest_unpruned_block: 15,
|
||||
oldest_block_to_keep: 15,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn import_custom_block<S: Storage>(
|
||||
storage: &mut S,
|
||||
validators: &[SecretKey],
|
||||
header: AuraHeader,
|
||||
) -> Result<HeaderId, Error> {
|
||||
let id = header.compute_id();
|
||||
import_header(
|
||||
storage,
|
||||
&mut KeepSomeHeadersBehindBest::default(),
|
||||
&test_aura_config(),
|
||||
&ValidatorsConfiguration::Single(ValidatorsSource::Contract(
|
||||
[0; 20].into(),
|
||||
validators.iter().map(secret_to_address).collect(),
|
||||
)),
|
||||
None,
|
||||
header,
|
||||
&(),
|
||||
None,
|
||||
)
|
||||
.map(|_| id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_of_non_best_block_may_finalize_blocks() {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// insert headers (H1, validator1), (H2, validator1), (H3, validator1)
|
||||
// making H3 the best header, without finalizing anything (we need 2 signatures)
|
||||
let mut expected_best_block = Default::default();
|
||||
for i in 1..4 {
|
||||
let step = 1 + i * TOTAL_VALIDATORS as u64;
|
||||
expected_best_block = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(i - 1)
|
||||
.step(step)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let (best_block, best_difficulty) = storage.best_block();
|
||||
assert_eq!(best_block, expected_best_block);
|
||||
assert_eq!(storage.finalized_block(), ctx.genesis.compute_id());
|
||||
|
||||
// insert headers (H1', validator1), (H2', validator2), finalizing H2, even though H3
|
||||
// has better difficulty than H2' (because there are more steps involved)
|
||||
let mut expected_finalized_block = Default::default();
|
||||
let mut parent_hash = ctx.genesis.compute_hash();
|
||||
for i in 1..3 {
|
||||
let step = i;
|
||||
let id = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_hash(parent_hash)
|
||||
.step(step)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
parent_hash = id.hash;
|
||||
if i == 1 {
|
||||
expected_finalized_block = id;
|
||||
}
|
||||
}
|
||||
let (new_best_block, new_best_difficulty) = storage.best_block();
|
||||
assert_eq!(new_best_block, expected_best_block);
|
||||
assert_eq!(new_best_difficulty, best_difficulty);
|
||||
assert_eq!(storage.finalized_block(), expected_finalized_block);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_to_unfinalized_fork_fails() {
|
||||
const VALIDATORS: u64 = 5;
|
||||
run_test(VALIDATORS as usize, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// header1, authored by validator[2] is best common block between two competing forks
|
||||
let header1 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(0)
|
||||
.step(2)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header1);
|
||||
assert_eq!(storage.finalized_block().number, 0);
|
||||
|
||||
// validator[3] has authored header2 (nothing is finalized yet)
|
||||
let header2 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(1)
|
||||
.step(3)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header2);
|
||||
assert_eq!(storage.finalized_block().number, 0);
|
||||
|
||||
// validator[4] has authored header3 (header1 is finalized)
|
||||
let header3 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(2)
|
||||
.step(4)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header3);
|
||||
assert_eq!(storage.finalized_block(), header1);
|
||||
|
||||
// validator[4] has authored 4 blocks: header2'...header5' (header1 is still finalized)
|
||||
let header2_1 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(1)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.step(4)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
let header3_1 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_hash(header2_1.hash)
|
||||
.step(4 + VALIDATORS)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
let header4_1 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_hash(header3_1.hash)
|
||||
.step(4 + VALIDATORS * 2)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
let header5_1 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_hash(header4_1.hash)
|
||||
.step(4 + VALIDATORS * 3)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header5_1);
|
||||
assert_eq!(storage.finalized_block(), header1);
|
||||
|
||||
// when we import header4 { parent = header3 }, authored by validator[0], header2 is finalized
|
||||
let header4 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(3)
|
||||
.step(5)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header5_1);
|
||||
assert_eq!(storage.finalized_block(), header2);
|
||||
|
||||
// when we import header5 { parent = header4 }, authored by validator[1], header3 is finalized
|
||||
let header5 = import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_hash(header4.hash)
|
||||
.step(6)
|
||||
.sign_by_set(&ctx.validators),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(storage.best_block().0, header5);
|
||||
assert_eq!(storage.finalized_block(), header3);
|
||||
|
||||
// import of header2'' { parent = header1 } fails, because it has number < best_finalized
|
||||
assert_eq!(
|
||||
import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(1)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.step(3)
|
||||
.sign_by_set(&ctx.validators)
|
||||
),
|
||||
Err(Error::AncientHeader),
|
||||
);
|
||||
|
||||
// import of header6' should also fail because we're trying to append to fork thas
|
||||
// has forked before finalized block
|
||||
assert_eq!(
|
||||
import_custom_block(
|
||||
&mut storage,
|
||||
&ctx.validators,
|
||||
HeaderBuilder::with_parent_number(5)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.step(5 + VALIDATORS * 4)
|
||||
.sign_by_set(&ctx.validators),
|
||||
),
|
||||
Err(Error::TryingToFinalizeSibling),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
pub use crate::test_utils::{insert_header, validator_utils::*, validators_change_receipt, HeaderBuilder, GAS_LIMIT};
|
||||
pub use bp_eth_poa::signatures::secret_to_address;
|
||||
|
||||
use crate::validators::{ValidatorsConfiguration, ValidatorsSource};
|
||||
use crate::{AuraConfiguration, ChainTime, Config, GenesisConfig as CrateGenesisConfig, PruningStrategy};
|
||||
use bp_eth_poa::{Address, AuraHeader, H256, U256};
|
||||
use frame_support::{parameter_types, weights::Weight};
|
||||
use secp256k1::SecretKey;
|
||||
use sp_runtime::{
|
||||
testing::Header as SubstrateHeader,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
pub type AccountId = u64;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||
|
||||
use crate as pallet_ethereum;
|
||||
|
||||
frame_support::construct_runtime! {
|
||||
pub enum TestRuntime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Module, Call, Config, Storage, Event<T>},
|
||||
Ethereum: pallet_ethereum::{Module, Call},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = Call;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = SubstrateHeader;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type BaseCallFilter = ();
|
||||
type SystemWeightInfo = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const TestFinalityVotesCachingInterval: Option<u64> = Some(16);
|
||||
pub TestAuraConfiguration: AuraConfiguration = test_aura_config();
|
||||
pub TestValidatorsConfiguration: ValidatorsConfiguration = test_validators_config();
|
||||
}
|
||||
|
||||
impl Config for TestRuntime {
|
||||
type AuraConfiguration = TestAuraConfiguration;
|
||||
type ValidatorsConfiguration = TestValidatorsConfiguration;
|
||||
type FinalityVotesCachingInterval = TestFinalityVotesCachingInterval;
|
||||
type PruningStrategy = KeepSomeHeadersBehindBest;
|
||||
type ChainTime = ConstChainTime;
|
||||
type OnHeadersSubmitted = ();
|
||||
}
|
||||
|
||||
/// Test context.
|
||||
pub struct TestContext {
|
||||
/// Initial (genesis) header.
|
||||
pub genesis: AuraHeader,
|
||||
/// Number of initial validators.
|
||||
pub total_validators: usize,
|
||||
/// Secret keys of validators, ordered by validator index.
|
||||
pub validators: Vec<SecretKey>,
|
||||
/// Addresses of validators, ordered by validator index.
|
||||
pub addresses: Vec<Address>,
|
||||
}
|
||||
|
||||
/// Aura configuration that is used in tests by default.
|
||||
pub fn test_aura_config() -> AuraConfiguration {
|
||||
AuraConfiguration {
|
||||
empty_steps_transition: u64::max_value(),
|
||||
strict_empty_steps_transition: 0,
|
||||
validate_step_transition: 0x16e360,
|
||||
validate_score_transition: 0x41a3c4,
|
||||
two_thirds_majority_transition: u64::max_value(),
|
||||
min_gas_limit: 0x1388.into(),
|
||||
max_gas_limit: U256::max_value(),
|
||||
maximum_extra_data_size: 0x20,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validators configuration that is used in tests by default.
|
||||
pub fn test_validators_config() -> ValidatorsConfiguration {
|
||||
ValidatorsConfiguration::Single(ValidatorsSource::List(validators_addresses(3)))
|
||||
}
|
||||
|
||||
/// Genesis header that is used in tests by default.
|
||||
pub fn genesis() -> AuraHeader {
|
||||
HeaderBuilder::genesis().sign_by(&validator(0))
|
||||
}
|
||||
|
||||
/// Run test with default genesis header.
|
||||
pub fn run_test<T>(total_validators: usize, test: impl FnOnce(TestContext) -> T) -> T {
|
||||
run_test_with_genesis(genesis(), total_validators, test)
|
||||
}
|
||||
|
||||
/// Run test with default genesis header.
|
||||
pub fn run_test_with_genesis<T>(
|
||||
genesis: AuraHeader,
|
||||
total_validators: usize,
|
||||
test: impl FnOnce(TestContext) -> T,
|
||||
) -> T {
|
||||
let validators = validators(total_validators);
|
||||
let addresses = validators_addresses(total_validators);
|
||||
sp_io::TestExternalities::new(
|
||||
CrateGenesisConfig {
|
||||
initial_header: genesis.clone(),
|
||||
initial_difficulty: 0.into(),
|
||||
initial_validators: addresses.clone(),
|
||||
}
|
||||
.build_storage::<TestRuntime, crate::DefaultInstance>()
|
||||
.unwrap(),
|
||||
)
|
||||
.execute_with(|| {
|
||||
test(TestContext {
|
||||
genesis,
|
||||
total_validators,
|
||||
validators,
|
||||
addresses,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Pruning strategy that keeps 10 headers behind best block.
|
||||
pub struct KeepSomeHeadersBehindBest(pub u64);
|
||||
|
||||
impl Default for KeepSomeHeadersBehindBest {
|
||||
fn default() -> KeepSomeHeadersBehindBest {
|
||||
KeepSomeHeadersBehindBest(10)
|
||||
}
|
||||
}
|
||||
|
||||
impl PruningStrategy for KeepSomeHeadersBehindBest {
|
||||
fn pruning_upper_bound(&mut self, best_number: u64, _: u64) -> u64 {
|
||||
best_number.saturating_sub(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Constant chain time
|
||||
#[derive(Default)]
|
||||
pub struct ConstChainTime;
|
||||
|
||||
impl ChainTime for ConstChainTime {
|
||||
fn is_timestamp_ahead(&self, timestamp: u64) -> bool {
|
||||
let now = i32::max_value() as u64 / 2;
|
||||
timestamp > now
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for testing and benchmarking the Ethereum Bridge Pallet.
|
||||
//!
|
||||
//! Although the name implies that it is used by tests, it shouldn't be be used _directly_ by tests.
|
||||
//! Instead these utilities should be used by the Mock runtime, which in turn is used by tests.
|
||||
//!
|
||||
//! On the other hand, they may be used directly by the bechmarking module.
|
||||
|
||||
// Since this is test code it's fine that not everything is used
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::finality::FinalityVotes;
|
||||
use crate::validators::CHANGE_EVENT_HASH;
|
||||
use crate::verification::calculate_score;
|
||||
use crate::{Config, HeaderToImport, Storage};
|
||||
|
||||
use bp_eth_poa::{
|
||||
rlp_encode,
|
||||
signatures::{secret_to_address, sign, SignHeader},
|
||||
Address, AuraHeader, Bloom, Receipt, SealedEmptyStep, H256, U256,
|
||||
};
|
||||
use secp256k1::SecretKey;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Gas limit valid in test environment.
|
||||
pub const GAS_LIMIT: u64 = 0x2000;
|
||||
|
||||
/// Test header builder.
|
||||
pub struct HeaderBuilder {
|
||||
header: AuraHeader,
|
||||
parent_header: AuraHeader,
|
||||
}
|
||||
|
||||
impl HeaderBuilder {
|
||||
/// Creates default genesis header.
|
||||
pub fn genesis() -> Self {
|
||||
let current_step = 0u64;
|
||||
Self {
|
||||
header: AuraHeader {
|
||||
gas_limit: GAS_LIMIT.into(),
|
||||
seal: vec![bp_eth_poa::rlp_encode(¤t_step).to_vec(), vec![]],
|
||||
..Default::default()
|
||||
},
|
||||
parent_header: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates default header on top of test parent with given hash.
|
||||
#[cfg(test)]
|
||||
pub fn with_parent_hash(parent_hash: H256) -> Self {
|
||||
Self::with_parent_hash_on_runtime::<crate::mock::TestRuntime, crate::DefaultInstance>(parent_hash)
|
||||
}
|
||||
|
||||
/// Creates default header on top of test parent with given number. First parent is selected.
|
||||
#[cfg(test)]
|
||||
pub fn with_parent_number(parent_number: u64) -> Self {
|
||||
Self::with_parent_number_on_runtime::<crate::mock::TestRuntime, crate::DefaultInstance>(parent_number)
|
||||
}
|
||||
|
||||
/// Creates default header on top of parent with given hash.
|
||||
pub fn with_parent_hash_on_runtime<T: Config<I>, I: crate::Instance>(parent_hash: H256) -> Self {
|
||||
use crate::Headers;
|
||||
use frame_support::StorageMap;
|
||||
|
||||
let parent_header = Headers::<T, I>::get(&parent_hash).unwrap().header;
|
||||
Self::with_parent(&parent_header)
|
||||
}
|
||||
|
||||
/// Creates default header on top of parent with given number. First parent is selected.
|
||||
pub fn with_parent_number_on_runtime<T: Config<I>, I: crate::Instance>(parent_number: u64) -> Self {
|
||||
use crate::HeadersByNumber;
|
||||
use frame_support::StorageMap;
|
||||
|
||||
let parent_hash = HeadersByNumber::<I>::get(parent_number).unwrap()[0];
|
||||
Self::with_parent_hash_on_runtime::<T, I>(parent_hash)
|
||||
}
|
||||
|
||||
/// Creates default header on top of non-existent parent.
|
||||
#[cfg(test)]
|
||||
pub fn with_number(number: u64) -> Self {
|
||||
Self::with_parent(&AuraHeader {
|
||||
number: number - 1,
|
||||
seal: vec![bp_eth_poa::rlp_encode(&(number - 1)).to_vec(), vec![]],
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates default header on top of given parent.
|
||||
pub fn with_parent(parent_header: &AuraHeader) -> Self {
|
||||
let parent_step = parent_header.step().unwrap();
|
||||
let current_step = parent_step + 1;
|
||||
Self {
|
||||
header: AuraHeader {
|
||||
parent_hash: parent_header.compute_hash(),
|
||||
number: parent_header.number + 1,
|
||||
gas_limit: GAS_LIMIT.into(),
|
||||
seal: vec![bp_eth_poa::rlp_encode(¤t_step).to_vec(), vec![]],
|
||||
difficulty: calculate_score(parent_step, current_step, 0),
|
||||
..Default::default()
|
||||
},
|
||||
parent_header: parent_header.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update step of this header.
|
||||
pub fn step(mut self, step: u64) -> Self {
|
||||
let parent_step = self.parent_header.step();
|
||||
self.header.seal[0] = rlp_encode(&step).to_vec();
|
||||
self.header.difficulty = parent_step
|
||||
.map(|parent_step| calculate_score(parent_step, step, 0))
|
||||
.unwrap_or_default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds empty steps to this header.
|
||||
pub fn empty_steps(mut self, empty_steps: &[(&SecretKey, u64)]) -> Self {
|
||||
let sealed_empty_steps = empty_steps
|
||||
.iter()
|
||||
.map(|(author, step)| {
|
||||
let mut empty_step = SealedEmptyStep {
|
||||
step: *step,
|
||||
signature: Default::default(),
|
||||
};
|
||||
let message = empty_step.message(&self.header.parent_hash);
|
||||
let signature: [u8; 65] = sign(author, message).into();
|
||||
empty_step.signature = signature.into();
|
||||
empty_step
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// by default in test configuration headers are generated without empty steps seal
|
||||
if self.header.seal.len() < 3 {
|
||||
self.header.seal.push(Vec::new());
|
||||
}
|
||||
|
||||
self.header.seal[2] = SealedEmptyStep::rlp_of(&sealed_empty_steps);
|
||||
self
|
||||
}
|
||||
|
||||
/// Update difficulty field of this header.
|
||||
pub fn difficulty(mut self, difficulty: U256) -> Self {
|
||||
self.header.difficulty = difficulty;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update extra data field of this header.
|
||||
pub fn extra_data(mut self, extra_data: Vec<u8>) -> Self {
|
||||
self.header.extra_data = extra_data;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update gas limit field of this header.
|
||||
pub fn gas_limit(mut self, gas_limit: U256) -> Self {
|
||||
self.header.gas_limit = gas_limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update gas used field of this header.
|
||||
pub fn gas_used(mut self, gas_used: U256) -> Self {
|
||||
self.header.gas_used = gas_used;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update log bloom field of this header.
|
||||
pub fn log_bloom(mut self, log_bloom: Bloom) -> Self {
|
||||
self.header.log_bloom = log_bloom;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update receipts root field of this header.
|
||||
pub fn receipts_root(mut self, receipts_root: H256) -> Self {
|
||||
self.header.receipts_root = receipts_root;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update timestamp field of this header.
|
||||
pub fn timestamp(mut self, timestamp: u64) -> Self {
|
||||
self.header.timestamp = timestamp;
|
||||
self
|
||||
}
|
||||
|
||||
/// Update transactions root field of this header.
|
||||
pub fn transactions_root(mut self, transactions_root: H256) -> Self {
|
||||
self.header.transactions_root = transactions_root;
|
||||
self
|
||||
}
|
||||
|
||||
/// Signs header by given author.
|
||||
pub fn sign_by(self, author: &SecretKey) -> AuraHeader {
|
||||
self.header.sign_by(author)
|
||||
}
|
||||
|
||||
/// Signs header by given authors set.
|
||||
pub fn sign_by_set(self, authors: &[SecretKey]) -> AuraHeader {
|
||||
self.header.sign_by_set(authors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for getting a genesis header which has been signed by an authority.
|
||||
pub fn build_genesis_header(author: &SecretKey) -> AuraHeader {
|
||||
let genesis = HeaderBuilder::genesis();
|
||||
genesis.header.sign_by(&author)
|
||||
}
|
||||
|
||||
/// Helper function for building a custom child header which has been signed by an authority.
|
||||
pub fn build_custom_header<F>(author: &SecretKey, previous: &AuraHeader, customize_header: F) -> AuraHeader
|
||||
where
|
||||
F: FnOnce(AuraHeader) -> AuraHeader,
|
||||
{
|
||||
let new_header = HeaderBuilder::with_parent(&previous);
|
||||
let custom_header = customize_header(new_header.header);
|
||||
custom_header.sign_by(author)
|
||||
}
|
||||
|
||||
/// Insert unverified header into storage.
|
||||
///
|
||||
/// This function assumes that the header is signed by validator from the current set.
|
||||
pub fn insert_header<S: Storage>(storage: &mut S, header: AuraHeader) {
|
||||
let id = header.compute_id();
|
||||
let best_finalized = storage.finalized_block();
|
||||
let import_context = storage.import_context(None, &header.parent_hash).unwrap();
|
||||
let parent_finality_votes = storage.cached_finality_votes(&header.parent_id().unwrap(), &best_finalized, |_| false);
|
||||
let finality_votes = crate::finality::prepare_votes(
|
||||
parent_finality_votes,
|
||||
best_finalized,
|
||||
&import_context.validators_set().validators.iter().collect(),
|
||||
id,
|
||||
&header,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
storage.insert_header(HeaderToImport {
|
||||
context: storage.import_context(None, &header.parent_hash).unwrap(),
|
||||
is_best: true,
|
||||
id,
|
||||
header,
|
||||
total_difficulty: 0.into(),
|
||||
enacted_change: None,
|
||||
scheduled_change: None,
|
||||
finality_votes,
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert unverified header into storage.
|
||||
///
|
||||
/// No assumptions about header author are made. The cost is that finality votes cache
|
||||
/// is filled incorrectly, so this function shall not be used if you're going to insert
|
||||
/// (or import) header descendants.
|
||||
pub fn insert_dummy_header<S: Storage>(storage: &mut S, header: AuraHeader) {
|
||||
storage.insert_header(HeaderToImport {
|
||||
context: storage.import_context(None, &header.parent_hash).unwrap(),
|
||||
is_best: true,
|
||||
id: header.compute_id(),
|
||||
header,
|
||||
total_difficulty: 0.into(),
|
||||
enacted_change: None,
|
||||
scheduled_change: None,
|
||||
finality_votes: FinalityVotes::default(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn validators_change_receipt(parent_hash: H256) -> Receipt {
|
||||
use bp_eth_poa::{LogEntry, TransactionOutcome};
|
||||
|
||||
Receipt {
|
||||
gas_used: 0.into(),
|
||||
log_bloom: (&[0xff; 256]).into(),
|
||||
outcome: TransactionOutcome::Unknown,
|
||||
logs: vec![LogEntry {
|
||||
address: [3; 20].into(),
|
||||
topics: vec![CHANGE_EVENT_HASH.into(), parent_hash],
|
||||
data: vec![
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
],
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub mod validator_utils {
|
||||
use super::*;
|
||||
|
||||
/// Return key pair of given test validator.
|
||||
pub fn validator(index: usize) -> SecretKey {
|
||||
let mut raw_secret = [0u8; 32];
|
||||
raw_secret[..8].copy_from_slice(&(index + 1).to_le_bytes());
|
||||
SecretKey::parse(&raw_secret).unwrap()
|
||||
}
|
||||
|
||||
/// Return key pairs of all test validators.
|
||||
pub fn validators(count: usize) -> Vec<SecretKey> {
|
||||
(0..count).map(validator).collect()
|
||||
}
|
||||
|
||||
/// Return address of test validator.
|
||||
pub fn validator_address(index: usize) -> Address {
|
||||
secret_to_address(&validator(index))
|
||||
}
|
||||
|
||||
/// Return addresses of all test validators.
|
||||
pub fn validators_addresses(count: usize) -> Vec<Address> {
|
||||
(0..count).map(validator_address).collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::{ChangeToEnact, Storage};
|
||||
use bp_eth_poa::{Address, AuraHeader, HeaderId, LogEntry, Receipt, U256};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// The hash of InitiateChange event of the validators set contract.
|
||||
pub(crate) const CHANGE_EVENT_HASH: &[u8; 32] = &[
|
||||
0x55, 0x25, 0x2f, 0xa6, 0xee, 0xe4, 0x74, 0x1b, 0x4e, 0x24, 0xa7, 0x4a, 0x70, 0xe9, 0xc1, 0x1f, 0xd2, 0xc2, 0x28,
|
||||
0x1d, 0xf8, 0xd6, 0xea, 0x13, 0x12, 0x6f, 0xf8, 0x45, 0xf7, 0x82, 0x5c, 0x89,
|
||||
];
|
||||
|
||||
/// Where source of validators addresses come from. This covers the chain lifetime.
|
||||
pub enum ValidatorsConfiguration {
|
||||
/// There's a single source for the whole chain lifetime.
|
||||
Single(ValidatorsSource),
|
||||
/// Validators source changes at given blocks. The blocks are ordered
|
||||
/// by the block number.
|
||||
Multi(Vec<(u64, ValidatorsSource)>),
|
||||
}
|
||||
|
||||
/// Where validators addresses come from.
|
||||
///
|
||||
/// This source is valid within some blocks range. The blocks range could
|
||||
/// cover multiple epochs - i.e. the validators that are authoring blocks
|
||||
/// within this range could change, but the source itself can not.
|
||||
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
|
||||
pub enum ValidatorsSource {
|
||||
/// The validators addresses are hardcoded and never change.
|
||||
List(Vec<Address>),
|
||||
/// The validators addresses are determined by the validators set contract
|
||||
/// deployed at given address. The contract must implement the `ValidatorSet`
|
||||
/// interface. Additionally, the initial validators set must be provided.
|
||||
Contract(Address, Vec<Address>),
|
||||
}
|
||||
|
||||
/// A short hand for optional validators change.
|
||||
pub type ValidatorsChange = Option<Vec<Address>>;
|
||||
|
||||
/// Validators manager.
|
||||
pub struct Validators<'a> {
|
||||
config: &'a ValidatorsConfiguration,
|
||||
}
|
||||
|
||||
impl<'a> Validators<'a> {
|
||||
/// Creates new validators manager using given configuration.
|
||||
pub fn new(config: &'a ValidatorsConfiguration) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
/// Returns true if header (probabilistically) signals validators change and
|
||||
/// the caller needs to provide transactions receipts to import the header.
|
||||
pub fn maybe_signals_validators_change(&self, header: &AuraHeader) -> bool {
|
||||
let (_, _, source) = self.source_at(header.number);
|
||||
|
||||
// if we are taking validators set from the fixed list, there's always
|
||||
// single epoch
|
||||
// => we never require transactions receipts
|
||||
let contract_address = match source {
|
||||
ValidatorsSource::List(_) => return false,
|
||||
ValidatorsSource::Contract(contract_address, _) => contract_address,
|
||||
};
|
||||
|
||||
// else we need to check logs bloom and if it has required bits set, it means
|
||||
// that the contract has (probably) emitted epoch change event
|
||||
let expected_bloom = LogEntry {
|
||||
address: *contract_address,
|
||||
topics: vec![CHANGE_EVENT_HASH.into(), header.parent_hash],
|
||||
data: Vec::new(), // irrelevant for bloom.
|
||||
}
|
||||
.bloom();
|
||||
|
||||
header.log_bloom.contains(&expected_bloom)
|
||||
}
|
||||
|
||||
/// Extracts validators change signal from the header.
|
||||
///
|
||||
/// Returns tuple where first element is the change scheduled by this header
|
||||
/// (i.e. this change is only applied starting from the block that has finalized
|
||||
/// current block). The second element is the immediately applied change.
|
||||
pub fn extract_validators_change(
|
||||
&self,
|
||||
header: &AuraHeader,
|
||||
receipts: Option<Vec<Receipt>>,
|
||||
) -> Result<(ValidatorsChange, ValidatorsChange), Error> {
|
||||
// let's first check if new source is starting from this header
|
||||
let (source_index, _, source) = self.source_at(header.number);
|
||||
let (next_starts_at, next_source) = self.source_at_next_header(source_index, header.number);
|
||||
if next_starts_at == header.number {
|
||||
match *next_source {
|
||||
ValidatorsSource::List(ref new_list) => return Ok((None, Some(new_list.clone()))),
|
||||
ValidatorsSource::Contract(_, ref new_list) => return Ok((Some(new_list.clone()), None)),
|
||||
}
|
||||
}
|
||||
|
||||
// else deal with previous source
|
||||
//
|
||||
// if we are taking validators set from the fixed list, there's always
|
||||
// single epoch
|
||||
// => we never require transactions receipts
|
||||
let contract_address = match source {
|
||||
ValidatorsSource::List(_) => return Ok((None, None)),
|
||||
ValidatorsSource::Contract(contract_address, _) => contract_address,
|
||||
};
|
||||
|
||||
// else we need to check logs bloom and if it has required bits set, it means
|
||||
// that the contract has (probably) emitted epoch change event
|
||||
let expected_bloom = LogEntry {
|
||||
address: *contract_address,
|
||||
topics: vec![CHANGE_EVENT_HASH.into(), header.parent_hash],
|
||||
data: Vec::new(), // irrelevant for bloom.
|
||||
}
|
||||
.bloom();
|
||||
|
||||
if !header.log_bloom.contains(&expected_bloom) {
|
||||
return Ok((None, None));
|
||||
}
|
||||
|
||||
let receipts = receipts.ok_or(Error::MissingTransactionsReceipts)?;
|
||||
if header.check_receipts_root(&receipts).is_err() {
|
||||
return Err(Error::TransactionsReceiptsMismatch);
|
||||
}
|
||||
|
||||
// iterate in reverse because only the _last_ change in a given
|
||||
// block actually has any effect
|
||||
Ok((
|
||||
receipts
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|r| r.log_bloom.contains(&expected_bloom))
|
||||
.flat_map(|r| r.logs.iter())
|
||||
.filter(|l| {
|
||||
l.address == *contract_address
|
||||
&& l.topics.len() == 2 && l.topics[0].as_fixed_bytes() == CHANGE_EVENT_HASH
|
||||
&& l.topics[1] == header.parent_hash
|
||||
})
|
||||
.filter_map(|l| {
|
||||
let data_len = l.data.len();
|
||||
if data_len < 64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_validators_len_u256 = U256::from_big_endian(&l.data[32..64]);
|
||||
let new_validators_len = new_validators_len_u256.low_u64();
|
||||
if new_validators_len_u256 != new_validators_len.into() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if (data_len - 64) as u64 != new_validators_len.saturating_mul(32) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
l.data[64..]
|
||||
.chunks(32)
|
||||
.map(|chunk| {
|
||||
let mut new_validator = Address::default();
|
||||
new_validator.as_mut().copy_from_slice(&chunk[12..32]);
|
||||
new_validator
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.next(),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
/// Finalize changes when blocks are finalized.
|
||||
pub fn finalize_validators_change<S: Storage>(
|
||||
&self,
|
||||
storage: &S,
|
||||
finalized_blocks: &[(HeaderId, Option<S::Submitter>)],
|
||||
) -> Option<ChangeToEnact> {
|
||||
// if we haven't finalized any blocks, no changes may be finalized
|
||||
let newest_finalized_id = match finalized_blocks.last().map(|(id, _)| id) {
|
||||
Some(last_finalized_id) => last_finalized_id,
|
||||
None => return None,
|
||||
};
|
||||
let oldest_finalized_id = finalized_blocks
|
||||
.first()
|
||||
.map(|(id, _)| id)
|
||||
.expect("finalized_blocks is not empty; qed");
|
||||
|
||||
// try to directly go to the header that has scheduled last change
|
||||
//
|
||||
// if we're unable to create import context for some block, it means
|
||||
// that the header has already been pruned => it and its ancestors had
|
||||
// no scheduled changes
|
||||
//
|
||||
// if we're unable to find scheduled changes for some block, it means
|
||||
// that these changes have been finalized already
|
||||
storage
|
||||
.import_context(None, &newest_finalized_id.hash)
|
||||
.and_then(|context| context.last_signal_block())
|
||||
.and_then(|signal_block| {
|
||||
if signal_block.number >= oldest_finalized_id.number {
|
||||
Some(signal_block)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.and_then(|signal_block| {
|
||||
storage
|
||||
.scheduled_change(&signal_block.hash)
|
||||
.map(|change| ChangeToEnact {
|
||||
signal_block: Some(signal_block),
|
||||
validators: change.validators,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns source of validators that should author the header.
|
||||
fn source_at(&self, header_number: u64) -> (usize, u64, &ValidatorsSource) {
|
||||
match self.config {
|
||||
ValidatorsConfiguration::Single(ref source) => (0, 0, source),
|
||||
ValidatorsConfiguration::Multi(ref sources) => sources
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.find(|(_, &(begin, _))| begin < header_number)
|
||||
.map(|(i, (begin, source))| (sources.len() - 1 - i, *begin, source))
|
||||
.expect(
|
||||
"there's always entry for the initial block;\
|
||||
we do not touch any headers with number < initial block number; qed",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns source of validators that should author the next header.
|
||||
fn source_at_next_header(&self, header_source_index: usize, header_number: u64) -> (u64, &ValidatorsSource) {
|
||||
match self.config {
|
||||
ValidatorsConfiguration::Single(ref source) => (0, source),
|
||||
ValidatorsConfiguration::Multi(ref sources) => {
|
||||
let next_source_index = header_source_index + 1;
|
||||
if next_source_index < sources.len() {
|
||||
let next_source = &sources[next_source_index];
|
||||
if next_source.0 < header_number + 1 {
|
||||
return (next_source.0, &next_source.1);
|
||||
}
|
||||
}
|
||||
|
||||
let source = &sources[header_source_index];
|
||||
(source.0, &source.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorsSource {
|
||||
/// Returns initial validators set.
|
||||
pub fn initial_epoch_validators(&self) -> Vec<Address> {
|
||||
match self {
|
||||
ValidatorsSource::List(ref list) => list.clone(),
|
||||
ValidatorsSource::Contract(_, ref list) => list.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, validators_addresses, validators_change_receipt, TestRuntime};
|
||||
use crate::DefaultInstance;
|
||||
use crate::{AuraScheduledChange, BridgeStorage, Headers, ScheduledChanges, StoredHeader};
|
||||
use bp_eth_poa::compute_merkle_root;
|
||||
use frame_support::StorageMap;
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
#[test]
|
||||
fn source_at_works() {
|
||||
let config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
(100, ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
]);
|
||||
let validators = Validators::new(&config);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(99),
|
||||
(0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(0, 99),
|
||||
(0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(100),
|
||||
(0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(0, 100),
|
||||
(100, &ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(200),
|
||||
(1, 100, &ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(1, 200),
|
||||
(200, &ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_signals_validators_change_works() {
|
||||
// when contract is active, but bloom has no required bits set
|
||||
let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||
let validators = Validators::new(&config);
|
||||
let mut header = AuraHeader {
|
||||
number: u64::max_value(),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(!validators.maybe_signals_validators_change(&header));
|
||||
|
||||
// when contract is active and bloom has required bits set
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
assert!(validators.maybe_signals_validators_change(&header));
|
||||
|
||||
// when list is active and bloom has required bits set
|
||||
let config = ValidatorsConfiguration::Single(ValidatorsSource::List(vec![[42; 20].into()]));
|
||||
let validators = Validators::new(&config);
|
||||
assert!(!validators.maybe_signals_validators_change(&header));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_validators_change_works() {
|
||||
let config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
(100, ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
]);
|
||||
let validators = Validators::new(&config);
|
||||
let mut header = AuraHeader {
|
||||
number: 100,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// when we're at the block that switches to list source
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Ok((None, Some(vec![[2; 20].into()]))),
|
||||
);
|
||||
|
||||
// when we're inside list range
|
||||
header.number = 150;
|
||||
assert_eq!(validators.extract_validators_change(&header, None), Ok((None, None)),);
|
||||
|
||||
// when we're at the block that switches to contract source
|
||||
header.number = 200;
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Ok((Some(vec![[3; 20].into()]), None)),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// but we have no receipts
|
||||
header.number = 250;
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Err(Error::MissingTransactionsReceipts),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// but there's no change in receipts
|
||||
header.receipts_root = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(Vec::new())),
|
||||
Ok((None, None)),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// and there's change in receipts
|
||||
let receipts = vec![validators_change_receipt(Default::default())];
|
||||
header.receipts_root = compute_merkle_root(receipts.iter().map(|r| r.rlp()));
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(receipts)),
|
||||
Ok((Some(vec![[7; 20].into()]), None)),
|
||||
);
|
||||
|
||||
// when incorrect receipts root passed
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(Vec::new())),
|
||||
Err(Error::TransactionsReceiptsMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
fn try_finalize_with_scheduled_change(scheduled_at: Option<HeaderId>) -> Option<ChangeToEnact> {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||
let validators = Validators::new(&config);
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// when we're finailizing blocks 10...100
|
||||
let id10 = HeaderId {
|
||||
number: 10,
|
||||
hash: [10; 32].into(),
|
||||
};
|
||||
let id100 = HeaderId {
|
||||
number: 100,
|
||||
hash: [100; 32].into(),
|
||||
};
|
||||
let finalized_blocks = vec![(id10, None), (id100, None)];
|
||||
let header100 = StoredHeader::<u64> {
|
||||
submitter: None,
|
||||
header: AuraHeader {
|
||||
number: 100,
|
||||
..Default::default()
|
||||
},
|
||||
total_difficulty: 0.into(),
|
||||
next_validators_set_id: 0,
|
||||
last_signal_block: scheduled_at,
|
||||
};
|
||||
let scheduled_change = AuraScheduledChange {
|
||||
validators: validators_addresses(1),
|
||||
prev_signal_block: None,
|
||||
};
|
||||
Headers::<TestRuntime>::insert(id100.hash, header100);
|
||||
if let Some(scheduled_at) = scheduled_at {
|
||||
ScheduledChanges::<DefaultInstance>::insert(scheduled_at.hash, scheduled_change);
|
||||
}
|
||||
|
||||
validators.finalize_validators_change(&storage, &finalized_blocks)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_validators_change_finalizes_scheduled_change() {
|
||||
let id50 = HeaderId {
|
||||
number: 50,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
try_finalize_with_scheduled_change(Some(id50)),
|
||||
Some(ChangeToEnact {
|
||||
signal_block: Some(id50),
|
||||
validators: validators_addresses(1),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_validators_change_does_not_finalize_when_changes_are_not_scheduled() {
|
||||
assert_eq!(try_finalize_with_scheduled_change(None), None,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalize_validators_change_does_not_finalize_changes_when_they_are_outside_of_range() {
|
||||
let id5 = HeaderId {
|
||||
number: 5,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(try_finalize_with_scheduled_change(Some(id5)), None,);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,945 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common 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.
|
||||
|
||||
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||
use crate::{AuraConfiguration, AuraScheduledChange, ChainTime, ImportContext, PoolConfiguration, Storage};
|
||||
use bp_eth_poa::{
|
||||
public_to_address, step_validator, Address, AuraHeader, HeaderId, Receipt, SealedEmptyStep, H256, H520, U128, U256,
|
||||
};
|
||||
use codec::Encode;
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use sp_runtime::transaction_validity::TransactionTag;
|
||||
use sp_std::{vec, vec::Vec};
|
||||
|
||||
/// Pre-check to see if should try and import this header.
|
||||
/// Returns error if we should not try to import this block.
|
||||
/// Returns ID of passed header and best finalized header.
|
||||
pub fn is_importable_header<S: Storage>(storage: &S, header: &AuraHeader) -> Result<(HeaderId, HeaderId), Error> {
|
||||
// we never import any header that competes with finalized header
|
||||
let finalized_id = storage.finalized_block();
|
||||
if header.number <= finalized_id.number {
|
||||
return Err(Error::AncientHeader);
|
||||
}
|
||||
// we never import any header with known hash
|
||||
let id = header.compute_id();
|
||||
if storage.header(&id.hash).is_some() {
|
||||
return Err(Error::KnownHeader);
|
||||
}
|
||||
|
||||
Ok((id, finalized_id))
|
||||
}
|
||||
|
||||
/// Try accept unsigned aura header into transaction pool.
|
||||
///
|
||||
/// Returns required and provided tags.
|
||||
pub fn accept_aura_header_into_pool<S: Storage, CT: ChainTime>(
|
||||
storage: &S,
|
||||
config: &AuraConfiguration,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
pool_config: &PoolConfiguration,
|
||||
header: &AuraHeader,
|
||||
chain_time: &CT,
|
||||
receipts: Option<&Vec<Receipt>>,
|
||||
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
|
||||
// check if we can verify further
|
||||
let (header_id, _) = is_importable_header(storage, header)?;
|
||||
|
||||
// we can always do contextless checks
|
||||
contextless_checks(config, header, chain_time)?;
|
||||
|
||||
// we want to avoid having same headers twice in the pool
|
||||
// => we're strict about receipts here - if we need them, we require receipts to be Some,
|
||||
// otherwise we require receipts to be None
|
||||
let receipts_required = Validators::new(validators_config).maybe_signals_validators_change(header);
|
||||
match (receipts_required, receipts.is_some()) {
|
||||
(true, false) => return Err(Error::MissingTransactionsReceipts),
|
||||
(false, true) => return Err(Error::RedundantTransactionsReceipts),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// we do not want to have all future headers in the pool at once
|
||||
// => if we see header with number > maximal ever seen header number + LIMIT,
|
||||
// => we consider this transaction invalid, but only at this moment (we do not want to ban it)
|
||||
// => let's mark it as Unknown transaction
|
||||
let (best_id, _) = storage.best_block();
|
||||
let difference = header.number.saturating_sub(best_id.number);
|
||||
if difference > pool_config.max_future_number_difference {
|
||||
return Err(Error::UnsignedTooFarInTheFuture);
|
||||
}
|
||||
|
||||
// TODO: only accept new headers when we're at the tip of PoA chain
|
||||
// https://github.com/paritytech/parity-bridges-common/issues/38
|
||||
|
||||
// we want to see at most one header with given number from single authority
|
||||
// => every header is providing tag (block_number + authority)
|
||||
// => since only one tx in the pool can provide the same tag, they're auto-deduplicated
|
||||
let provides_number_and_authority_tag = (header.number, header.author).encode();
|
||||
|
||||
// we want to see several 'future' headers in the pool at once, but we may not have access to
|
||||
// previous headers here
|
||||
// => we can at least 'verify' that headers comprise a chain by providing and requiring
|
||||
// tag (header.number, header.hash)
|
||||
let provides_header_number_and_hash_tag = header_id.encode();
|
||||
|
||||
// depending on whether parent header is available, we either perform full or 'shortened' check
|
||||
let context = storage.import_context(None, &header.parent_hash);
|
||||
let tags = match context {
|
||||
Some(context) => {
|
||||
let header_step = contextual_checks(config, &context, None, header)?;
|
||||
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
||||
|
||||
// since our parent is already in the storage, we do not require it
|
||||
// to be in the transaction pool
|
||||
(
|
||||
vec![],
|
||||
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
|
||||
)
|
||||
}
|
||||
None => {
|
||||
// we know nothing about parent header
|
||||
// => the best thing we can do is to believe that there are no forks in
|
||||
// PoA chain AND that the header is produced either by previous, or next
|
||||
// scheduled validators set change
|
||||
let header_step = header.step().ok_or(Error::MissingStep)?;
|
||||
let best_context = storage.import_context(None, &best_id.hash).expect(
|
||||
"import context is None only when header is missing from the storage;\
|
||||
best header is always in the storage; qed",
|
||||
);
|
||||
let validators_check_result =
|
||||
validator_checks(config, &best_context.validators_set().validators, header, header_step);
|
||||
if let Err(error) = validators_check_result {
|
||||
find_next_validators_signal(storage, &best_context)
|
||||
.ok_or(error)
|
||||
.and_then(|next_validators| validator_checks(config, &next_validators, header, header_step))?;
|
||||
}
|
||||
|
||||
// since our parent is missing from the storage, we **DO** require it
|
||||
// to be in the transaction pool
|
||||
// (- 1 can't underflow because there's always best block in the header)
|
||||
let requires_header_number_and_hash_tag = HeaderId {
|
||||
number: header.number - 1,
|
||||
hash: header.parent_hash,
|
||||
}
|
||||
.encode();
|
||||
(
|
||||
vec![requires_header_number_and_hash_tag],
|
||||
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// the heaviest, but rare operation - we do not want invalid receipts in the pool
|
||||
if let Some(receipts) = receipts {
|
||||
frame_support::debug::trace!(target: "runtime", "Got receipts! {:?}", receipts);
|
||||
if header.check_receipts_root(receipts).is_err() {
|
||||
return Err(Error::TransactionsReceiptsMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
/// Verify header by Aura rules.
|
||||
pub fn verify_aura_header<S: Storage, CT: ChainTime>(
|
||||
storage: &S,
|
||||
config: &AuraConfiguration,
|
||||
submitter: Option<S::Submitter>,
|
||||
header: &AuraHeader,
|
||||
chain_time: &CT,
|
||||
) -> Result<ImportContext<S::Submitter>, Error> {
|
||||
// let's do the lightest check first
|
||||
contextless_checks(config, header, chain_time)?;
|
||||
|
||||
// the rest of checks requires access to the parent header
|
||||
let context = storage.import_context(submitter, &header.parent_hash).ok_or_else(|| {
|
||||
frame_support::debug::warn!(
|
||||
target: "runtime",
|
||||
"Missing parent PoA block: ({:?}, {})",
|
||||
header.number.checked_sub(1),
|
||||
header.parent_hash,
|
||||
);
|
||||
|
||||
Error::MissingParentBlock
|
||||
})?;
|
||||
let header_step = contextual_checks(config, &context, None, header)?;
|
||||
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
/// Perform basic checks that only require header itself.
|
||||
fn contextless_checks<CT: ChainTime>(
|
||||
config: &AuraConfiguration,
|
||||
header: &AuraHeader,
|
||||
chain_time: &CT,
|
||||
) -> Result<(), Error> {
|
||||
let expected_seal_fields = expected_header_seal_fields(config, header);
|
||||
if header.seal.len() != expected_seal_fields {
|
||||
return Err(Error::InvalidSealArity);
|
||||
}
|
||||
if header.number >= u64::max_value() {
|
||||
return Err(Error::RidiculousNumber);
|
||||
}
|
||||
if header.gas_used > header.gas_limit {
|
||||
return Err(Error::TooMuchGasUsed);
|
||||
}
|
||||
if header.gas_limit < config.min_gas_limit {
|
||||
return Err(Error::InvalidGasLimit);
|
||||
}
|
||||
if header.gas_limit > config.max_gas_limit {
|
||||
return Err(Error::InvalidGasLimit);
|
||||
}
|
||||
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
|
||||
return Err(Error::ExtraDataOutOfBounds);
|
||||
}
|
||||
|
||||
// we can't detect if block is from future in runtime
|
||||
// => let's only do an overflow check
|
||||
if header.timestamp > i32::max_value() as u64 {
|
||||
return Err(Error::TimestampOverflow);
|
||||
}
|
||||
|
||||
if chain_time.is_timestamp_ahead(header.timestamp) {
|
||||
return Err(Error::HeaderTimestampIsAhead);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform checks that require access to parent header.
|
||||
fn contextual_checks<Submitter>(
|
||||
config: &AuraConfiguration,
|
||||
context: &ImportContext<Submitter>,
|
||||
validators_override: Option<&[Address]>,
|
||||
header: &AuraHeader,
|
||||
) -> Result<u64, Error> {
|
||||
let validators = validators_override.unwrap_or_else(|| &context.validators_set().validators);
|
||||
let header_step = header.step().ok_or(Error::MissingStep)?;
|
||||
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
|
||||
|
||||
// Ensure header is from the step after context.
|
||||
if header_step == parent_step {
|
||||
return Err(Error::DoubleVote);
|
||||
}
|
||||
#[allow(clippy::suspicious_operation_groupings)]
|
||||
if header.number >= config.validate_step_transition && header_step < parent_step {
|
||||
return Err(Error::DoubleVote);
|
||||
}
|
||||
|
||||
// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
|
||||
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
|
||||
let empty_steps_len = match header.number >= config.empty_steps_transition {
|
||||
true => {
|
||||
let strict_empty_steps = header.number >= config.strict_empty_steps_transition;
|
||||
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
|
||||
let empty_steps_len = empty_steps.len();
|
||||
let mut prev_empty_step = 0;
|
||||
|
||||
for empty_step in empty_steps {
|
||||
if empty_step.step <= parent_step || empty_step.step >= header_step {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
if !verify_empty_step(&header.parent_hash, &empty_step, validators) {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
if strict_empty_steps {
|
||||
if empty_step.step <= prev_empty_step {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
prev_empty_step = empty_step.step;
|
||||
}
|
||||
}
|
||||
|
||||
empty_steps_len
|
||||
}
|
||||
false => 0,
|
||||
};
|
||||
|
||||
// Validate chain score.
|
||||
if header.number >= config.validate_score_transition {
|
||||
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
|
||||
if header.difficulty != expected_difficulty {
|
||||
return Err(Error::InvalidDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(header_step)
|
||||
}
|
||||
|
||||
/// Check that block is produced by expected validator.
|
||||
fn validator_checks(
|
||||
config: &AuraConfiguration,
|
||||
validators: &[Address],
|
||||
header: &AuraHeader,
|
||||
header_step: u64,
|
||||
) -> Result<(), Error> {
|
||||
let expected_validator = *step_validator(validators, header_step);
|
||||
if header.author != expected_validator {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
|
||||
let header_seal_hash = header
|
||||
.seal_hash(header.number >= config.empty_steps_transition)
|
||||
.ok_or(Error::MissingEmptySteps)?;
|
||||
let is_invalid_proposer = !verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
|
||||
if is_invalid_proposer {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns expected number of seal fields in the header.
|
||||
fn expected_header_seal_fields(config: &AuraConfiguration, header: &AuraHeader) -> usize {
|
||||
if header.number != u64::max_value() && header.number >= config.empty_steps_transition {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify single sealed empty step.
|
||||
fn verify_empty_step(parent_hash: &H256, step: &SealedEmptyStep, validators: &[Address]) -> bool {
|
||||
let expected_validator = *step_validator(validators, step.step);
|
||||
let message = step.message(parent_hash);
|
||||
verify_signature(&expected_validator, &step.signature, &message)
|
||||
}
|
||||
|
||||
/// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
||||
pub(crate) fn calculate_score(parent_step: u64, current_step: u64, current_empty_steps: usize) -> U256 {
|
||||
U256::from(U128::max_value()) + U256::from(parent_step) - U256::from(current_step) + U256::from(current_empty_steps)
|
||||
}
|
||||
|
||||
/// Verify that the signature over message has been produced by given validator.
|
||||
fn verify_signature(expected_validator: &Address, signature: &H520, message: &H256) -> bool {
|
||||
secp256k1_ecdsa_recover(signature.as_fixed_bytes(), message.as_fixed_bytes())
|
||||
.map(|public| public_to_address(&public))
|
||||
.map(|address| *expected_validator == address)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Find next unfinalized validators set change after finalized set.
|
||||
fn find_next_validators_signal<S: Storage>(storage: &S, context: &ImportContext<S::Submitter>) -> Option<Vec<Address>> {
|
||||
// that's the earliest block number we may met in following loop
|
||||
// it may be None if that's the first set
|
||||
let best_set_signal_block = context.validators_set().signal_block;
|
||||
|
||||
// if parent schedules validators set change, then it may be our set
|
||||
// else we'll start with last known change
|
||||
let mut current_set_signal_block = context.last_signal_block();
|
||||
let mut next_scheduled_set: Option<AuraScheduledChange> = None;
|
||||
|
||||
loop {
|
||||
// if we have reached block that signals finalized change, then
|
||||
// next_current_block_hash points to the block that schedules next
|
||||
// change
|
||||
let current_scheduled_set = match current_set_signal_block {
|
||||
Some(current_set_signal_block) if Some(¤t_set_signal_block) == best_set_signal_block.as_ref() => {
|
||||
return next_scheduled_set.map(|scheduled_set| scheduled_set.validators)
|
||||
}
|
||||
None => return next_scheduled_set.map(|scheduled_set| scheduled_set.validators),
|
||||
Some(current_set_signal_block) => storage.scheduled_change(¤t_set_signal_block.hash).expect(
|
||||
"header that is associated with this change is not pruned;\
|
||||
scheduled changes are only removed when header is pruned; qed",
|
||||
),
|
||||
};
|
||||
|
||||
current_set_signal_block = current_scheduled_set.prev_signal_block;
|
||||
next_scheduled_set = Some(current_scheduled_set);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
insert_header, run_test_with_genesis, test_aura_config, validator, validator_address, validators_addresses,
|
||||
validators_change_receipt, AccountId, ConstChainTime, HeaderBuilder, TestRuntime, GAS_LIMIT,
|
||||
};
|
||||
use crate::validators::ValidatorsSource;
|
||||
use crate::DefaultInstance;
|
||||
use crate::{
|
||||
pool_configuration, BridgeStorage, FinalizedBlock, Headers, HeadersByNumber, NextValidatorsSetId,
|
||||
ScheduledChanges, ValidatorsSet, ValidatorsSets,
|
||||
};
|
||||
use bp_eth_poa::{compute_merkle_root, rlp_encode, TransactionOutcome, H520, U256};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use hex_literal::hex;
|
||||
use secp256k1::SecretKey;
|
||||
use sp_runtime::transaction_validity::TransactionTag;
|
||||
|
||||
const GENESIS_STEP: u64 = 42;
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
fn genesis() -> AuraHeader {
|
||||
HeaderBuilder::genesis().step(GENESIS_STEP).sign_by(&validator(0))
|
||||
}
|
||||
|
||||
fn verify_with_config(config: &AuraConfiguration, header: &AuraHeader) -> Result<ImportContext<AccountId>, Error> {
|
||||
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
verify_aura_header(&storage, &config, None, header, &ConstChainTime::default())
|
||||
})
|
||||
}
|
||||
|
||||
fn default_verify(header: &AuraHeader) -> Result<ImportContext<AccountId>, Error> {
|
||||
verify_with_config(&test_aura_config(), header)
|
||||
}
|
||||
|
||||
fn default_accept_into_pool(
|
||||
mut make_header: impl FnMut(&[SecretKey]) -> (AuraHeader, Option<Vec<Receipt>>),
|
||||
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
|
||||
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let block1 = HeaderBuilder::with_parent_number(0).sign_by_set(&validators);
|
||||
insert_header(&mut storage, block1);
|
||||
let block2 = HeaderBuilder::with_parent_number(1).sign_by_set(&validators);
|
||||
let block2_id = block2.compute_id();
|
||||
insert_header(&mut storage, block2);
|
||||
let block3 = HeaderBuilder::with_parent_number(2).sign_by_set(&validators);
|
||||
insert_header(&mut storage, block3);
|
||||
|
||||
FinalizedBlock::<DefaultInstance>::put(block2_id);
|
||||
|
||||
let validators_config =
|
||||
ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||
let (header, receipts) = make_header(&validators);
|
||||
accept_aura_header_into_pool(
|
||||
&storage,
|
||||
&test_aura_config(),
|
||||
&validators_config,
|
||||
&pool_configuration(),
|
||||
&header,
|
||||
&(),
|
||||
receipts.as_ref(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn change_validators_set_at(number: u64, finalized_set: Vec<Address>, signalled_set: Option<Vec<Address>>) {
|
||||
let set_id = NextValidatorsSetId::<DefaultInstance>::get();
|
||||
NextValidatorsSetId::<DefaultInstance>::put(set_id + 1);
|
||||
ValidatorsSets::<DefaultInstance>::insert(
|
||||
set_id,
|
||||
ValidatorsSet {
|
||||
validators: finalized_set,
|
||||
signal_block: None,
|
||||
enact_block: HeaderId {
|
||||
number: 0,
|
||||
hash: HeadersByNumber::<DefaultInstance>::get(&0).unwrap()[0],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let header_hash = HeadersByNumber::<DefaultInstance>::get(&number).unwrap()[0];
|
||||
let mut header = Headers::<TestRuntime>::get(&header_hash).unwrap();
|
||||
header.next_validators_set_id = set_id;
|
||||
if let Some(signalled_set) = signalled_set {
|
||||
header.last_signal_block = Some(HeaderId {
|
||||
number: header.header.number - 1,
|
||||
hash: header.header.parent_hash,
|
||||
});
|
||||
ScheduledChanges::<DefaultInstance>::insert(
|
||||
header.header.parent_hash,
|
||||
AuraScheduledChange {
|
||||
validators: signalled_set,
|
||||
prev_signal_block: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Headers::<TestRuntime>::insert(header_hash, header);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_seal_count() {
|
||||
// when there are no seals at all
|
||||
let mut header = AuraHeader::default();
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's single seal (we expect 2 or 3 seals)
|
||||
header.seal = vec![vec![]];
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's 3 seals (we expect 2 by default)
|
||||
header.seal = vec![vec![], vec![], vec![]];
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's 2 seals
|
||||
header.seal = vec![vec![], vec![]];
|
||||
assert_ne!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_header_number() {
|
||||
// when number is u64::max_value()
|
||||
let header = HeaderBuilder::with_number(u64::max_value()).sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||
|
||||
// when header is < u64::max_value()
|
||||
let header = HeaderBuilder::with_number(u64::max_value() - 1).sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_gas_used() {
|
||||
// when gas used is larger than gas limit
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_used((GAS_LIMIT + 1).into())
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||
|
||||
// when gas used is less than gas limit
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_used((GAS_LIMIT - 1).into())
|
||||
.sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_gas_limit() {
|
||||
let mut config = test_aura_config();
|
||||
config.min_gas_limit = 100.into();
|
||||
config.max_gas_limit = 200.into();
|
||||
|
||||
// when limit is lower than expected
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_limit(50.into())
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
|
||||
// when limit is larger than expected
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_limit(250.into())
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
|
||||
// when limit is within expected range
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_limit(150.into())
|
||||
.sign_by(&validator(0));
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_extra_data_len() {
|
||||
// when extra data is too large
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.extra_data(std::iter::repeat(42).take(1000).collect::<Vec<_>>())
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||
|
||||
// when extra data size is OK
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.extra_data(std::iter::repeat(42).take(10).collect::<Vec<_>>())
|
||||
.sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_timestamp() {
|
||||
// when timestamp overflows i32
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.timestamp(i32::max_value() as u64 + 1)
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||
|
||||
// when timestamp doesn't overflow i32
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.timestamp(i32::max_value() as u64)
|
||||
.sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_chain_time() {
|
||||
// expected import context after verification
|
||||
let expect = ImportContext::<AccountId> {
|
||||
submitter: None,
|
||||
parent_hash: hex!("6e41bff05578fc1db17f6816117969b07d2217f1f9039d8116a82764335991d3").into(),
|
||||
parent_header: genesis(),
|
||||
parent_total_difficulty: U256::zero(),
|
||||
parent_scheduled_change: None,
|
||||
validators_set_id: 0,
|
||||
validators_set: ValidatorsSet {
|
||||
validators: vec![
|
||||
hex!("dc5b20847f43d67928f49cd4f85d696b5a7617b5").into(),
|
||||
hex!("897df33a7b3c62ade01e22c13d48f98124b4480f").into(),
|
||||
hex!("05c987b34c6ef74e0c7e69c6e641120c24164c2d").into(),
|
||||
],
|
||||
signal_block: None,
|
||||
enact_block: HeaderId {
|
||||
number: 0,
|
||||
hash: hex!("6e41bff05578fc1db17f6816117969b07d2217f1f9039d8116a82764335991d3").into(),
|
||||
},
|
||||
},
|
||||
last_signal_block: None,
|
||||
};
|
||||
|
||||
// header is behind
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.timestamp(i32::max_value() as u64 / 2 - 100)
|
||||
.sign_by(&validator(1));
|
||||
assert_eq!(default_verify(&header).unwrap(), expect);
|
||||
|
||||
// header is ahead
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.timestamp(i32::max_value() as u64 / 2 + 100)
|
||||
.sign_by(&validator(1));
|
||||
assert_eq!(default_verify(&header), Err(Error::HeaderTimestampIsAhead));
|
||||
|
||||
// header has same timestamp as ConstChainTime
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.timestamp(i32::max_value() as u64 / 2)
|
||||
.sign_by(&validator(1));
|
||||
assert_eq!(default_verify(&header).unwrap(), expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_parent_existence() {
|
||||
// when there's no parent in the storage
|
||||
let header = HeaderBuilder::with_number(1).sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
|
||||
// when parent is in the storage
|
||||
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_step() {
|
||||
// when step is missing from seals
|
||||
let mut header = AuraHeader {
|
||||
seal: vec![vec![], vec![]],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().compute_hash(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingStep));
|
||||
|
||||
// when step is the same as for the parent block
|
||||
header.seal[0] = rlp_encode(&42u64).to_vec();
|
||||
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal[0] = rlp_encode(&43u64).to_vec();
|
||||
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// now check with validate_step check enabled
|
||||
let mut config = test_aura_config();
|
||||
config.validate_step_transition = 0;
|
||||
|
||||
// when step is lesser that for the parent block
|
||||
header.seal[0] = rlp_encode(&40u64).to_vec();
|
||||
header.seal = vec![vec![40], vec![]];
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal[0] = rlp_encode(&44u64).to_vec();
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_empty_step() {
|
||||
let mut config = test_aura_config();
|
||||
config.empty_steps_transition = 0;
|
||||
|
||||
// when empty step duplicates parent step
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.empty_steps(&[(&validator(0), GENESIS_STEP)])
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by(&validator(3));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when empty step signature check fails
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.empty_steps(&[(&validator(100), GENESIS_STEP + 1)])
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by(&validator(3));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when we are accepting strict empty steps and they come not in order
|
||||
config.strict_empty_steps_transition = 0;
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.empty_steps(&[(&validator(2), GENESIS_STEP + 2), (&validator(1), GENESIS_STEP + 1)])
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by(&validator(3));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when empty steps are OK
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.empty_steps(&[(&validator(1), GENESIS_STEP + 1), (&validator(2), GENESIS_STEP + 2)])
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by(&validator(3));
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_chain_score() {
|
||||
let mut config = test_aura_config();
|
||||
config.validate_score_transition = 0;
|
||||
|
||||
// when chain score is invalid
|
||||
let header = HeaderBuilder::with_parent(&genesis())
|
||||
.difficulty(100.into())
|
||||
.sign_by(&validator(0));
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||
|
||||
// when chain score is accepted
|
||||
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_validator() {
|
||||
let good_header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(1));
|
||||
|
||||
// when header author is invalid
|
||||
let mut header = good_header.clone();
|
||||
header.author = Default::default();
|
||||
assert_eq!(default_verify(&header), Err(Error::NotValidator));
|
||||
|
||||
// when header signature is invalid
|
||||
let mut header = good_header.clone();
|
||||
header.seal[1] = rlp_encode(&H520::default()).to_vec();
|
||||
assert_eq!(default_verify(&header), Err(Error::NotValidator));
|
||||
|
||||
// when everything is OK
|
||||
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_known_blocks() {
|
||||
// when header is known
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (HeaderBuilder::with_parent_number(2).sign_by_set(validators), None)),
|
||||
Err(Error::KnownHeader),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_ancient_blocks() {
|
||||
// when header number is less than finalized
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
HeaderBuilder::with_parent_number(1)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.sign_by_set(validators),
|
||||
None,
|
||||
),),
|
||||
Err(Error::AncientHeader),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_rejects_headers_without_required_receipts() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|_| (
|
||||
AuraHeader {
|
||||
number: 20_000_000,
|
||||
seal: vec![vec![], vec![]],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
log_bloom: (&[0xff; 256]).into(),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
),),
|
||||
Err(Error::MissingTransactionsReceipts),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_rejects_headers_with_redundant_receipts() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
HeaderBuilder::with_parent_number(3).sign_by_set(validators),
|
||||
Some(vec![Receipt {
|
||||
gas_used: 1.into(),
|
||||
log_bloom: (&[0xff; 256]).into(),
|
||||
logs: vec![],
|
||||
outcome: TransactionOutcome::Unknown,
|
||||
}]),
|
||||
),),
|
||||
Err(Error::RedundantTransactionsReceipts),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_future_block_number() {
|
||||
// when header is too far from the future
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (HeaderBuilder::with_number(100).sign_by_set(&validators), None),),
|
||||
Err(Error::UnsignedTooFarInTheFuture),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_performs_full_verification_when_parent_is_known() {
|
||||
// if parent is known, then we'll execute contextual_checks, which
|
||||
// checks for DoubleVote
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
HeaderBuilder::with_parent_number(3)
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by_set(&validators),
|
||||
None,
|
||||
),),
|
||||
Err(Error::DoubleVote),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_performs_validators_checks_when_parent_is_unknown() {
|
||||
// if parent is unknown, then we still need to check if header has required signature
|
||||
// (even if header will be considered invalid/duplicate later, we can use this signature
|
||||
// as a proof of malicious action by this validator)
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|_| (HeaderBuilder::with_number(8).step(8).sign_by(&validator(1)), None,)),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_header_with_known_parent() {
|
||||
let mut hash = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = HeaderBuilder::with_parent_number(3).sign_by_set(validators);
|
||||
hash = Some(header.compute_hash());
|
||||
(header, None)
|
||||
}),
|
||||
Ok((
|
||||
// no tags are required
|
||||
vec![],
|
||||
// header provides two tags
|
||||
vec![
|
||||
(4u64, validators_addresses(3)[1]).encode(),
|
||||
(4u64, hash.unwrap()).encode(),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_header_with_unknown_parent() {
|
||||
let mut id = None;
|
||||
let mut parent_id = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = HeaderBuilder::with_number(5)
|
||||
.step(GENESIS_STEP + 5)
|
||||
.sign_by_set(validators);
|
||||
id = Some(header.compute_id());
|
||||
parent_id = header.parent_id();
|
||||
(header, None)
|
||||
}),
|
||||
Ok((
|
||||
// parent tag required
|
||||
vec![parent_id.unwrap().encode()],
|
||||
// header provides two tags
|
||||
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_uses_next_validators_set_when_finalized_fails() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|actual_validators| {
|
||||
// change finalized set at parent header
|
||||
change_validators_set_at(3, validators_addresses(1), None);
|
||||
|
||||
// header is signed using wrong set
|
||||
let header = HeaderBuilder::with_number(5)
|
||||
.step(GENESIS_STEP + 2)
|
||||
.sign_by_set(actual_validators);
|
||||
|
||||
(header, None)
|
||||
}),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
|
||||
let mut id = None;
|
||||
let mut parent_id = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|actual_validators| {
|
||||
// change finalized set at parent header + signal valid set at parent block
|
||||
change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3)));
|
||||
|
||||
// header is signed using wrong set
|
||||
let header = HeaderBuilder::with_number(5)
|
||||
.step(GENESIS_STEP + 2)
|
||||
.sign_by_set(actual_validators);
|
||||
id = Some(header.compute_id());
|
||||
parent_id = header.parent_id();
|
||||
|
||||
(header, None)
|
||||
}),
|
||||
Ok((
|
||||
// parent tag required
|
||||
vec![parent_id.unwrap().encode(),],
|
||||
// header provides two tags
|
||||
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_rejects_headers_with_invalid_receipts() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = HeaderBuilder::with_parent_number(3)
|
||||
.log_bloom((&[0xff; 256]).into())
|
||||
.sign_by_set(validators);
|
||||
(header, Some(vec![validators_change_receipt(Default::default())]))
|
||||
}),
|
||||
Err(Error::TransactionsReceiptsMismatch),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_accepts_headers_with_valid_receipts() {
|
||||
let mut hash = None;
|
||||
let receipts = vec![validators_change_receipt(Default::default())];
|
||||
let receipts_root = compute_merkle_root(receipts.iter().map(|r| r.rlp()));
|
||||
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = HeaderBuilder::with_parent_number(3)
|
||||
.log_bloom((&[0xff; 256]).into())
|
||||
.receipts_root(receipts_root)
|
||||
.sign_by_set(validators);
|
||||
hash = Some(header.compute_hash());
|
||||
(header, Some(receipts.clone()))
|
||||
}),
|
||||
Ok((
|
||||
// no tags are required
|
||||
vec![],
|
||||
// header provides two tags
|
||||
vec![
|
||||
(4u64, validators_addresses(3)[1]).encode(),
|
||||
(4u64, hash.unwrap()).encode(),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user