mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 19:47:59 +00:00
Runtime benchmarks: start (#136)
* runtime benchmarks: start * merge tests + benchmarks infrastructure * fix compilation * Fix compilation issues with runtime-benchmark feature flag Mainly involved pulling in correct dependencies and adding some functions which were called but didn't yet exist. * Fix broken compilation for tests * Move header signing methods into trait * Move signing related test helpers to own module * Remove comment about feature flag * Add constants to tests * Add top level comment for testing utilities Co-authored-by: Hernando Castano <castano.ha@gmail.com>
This commit is contained in:
committed by
Bastian Köcher
parent
ea45fa8da7
commit
e39ca0dc16
@@ -115,6 +115,16 @@ version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[dependencies.frame-benchmarking]
|
||||
version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[dependencies.frame-benchmarking-cli]
|
||||
version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = "3.1.0"
|
||||
|
||||
@@ -123,3 +133,14 @@ package = "substrate-build-script-utils"
|
||||
version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[build-dependencies.frame-benchmarking-cli]
|
||||
version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
runtime-benchmarks = [
|
||||
"bridge-node-runtime/runtime-benchmarks",
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 sc_cli::{RunCmd, Subcommand};
|
||||
use sc_cli::RunCmd;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
@@ -25,3 +25,15 @@ pub struct Cli {
|
||||
#[structopt(flatten)]
|
||||
pub run: RunCmd,
|
||||
}
|
||||
|
||||
/// Possible subcommands of the main binary.
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum Subcommand {
|
||||
/// A set of base subcommands handled by `sc_cli`.
|
||||
#[structopt(flatten)]
|
||||
Base(sc_cli::Subcommand),
|
||||
|
||||
/// The custom benchmark subcommmand benchmarking runtime pallets.
|
||||
#[structopt(name = "benchmark", about = "Benchmark runtime pallets.")]
|
||||
Benchmark(frame_benchmarking_cli::BenchmarkCmd),
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
// 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::cli::Cli;
|
||||
use crate::cli::{Cli, Subcommand};
|
||||
use crate::service;
|
||||
use bridge_node_runtime::Block;
|
||||
use sc_cli::SubstrateCli;
|
||||
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
|
||||
|
||||
@@ -81,7 +82,20 @@ pub fn run() -> sc_cli::Result<()> {
|
||||
let cli = Cli::from_args();
|
||||
|
||||
match &cli.subcommand {
|
||||
Some(subcommand) => {
|
||||
Some(Subcommand::Benchmark(cmd)) => {
|
||||
if cfg!(feature = "runtime-benchmarks") {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
|
||||
runner.sync_run(|config| cmd.run::<Block, service::Executor>(config))
|
||||
} else {
|
||||
println!(
|
||||
"Benchmarking wasn't enabled when building the node. \
|
||||
You can enable it with `--features runtime-benchmarks`."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some(Subcommand::Base(subcommand)) => {
|
||||
let runner = cli.create_runner(subcommand)?;
|
||||
runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0))
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ native_executor_instance!(
|
||||
pub Executor,
|
||||
bridge_node_runtime::api::dispatch,
|
||||
bridge_node_runtime::native_version,
|
||||
frame_benchmarking::benchmarking::HostFunctions,
|
||||
);
|
||||
|
||||
/// Starts a `ServiceBuilder` for a full service.
|
||||
|
||||
@@ -195,6 +195,13 @@ default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[dependencies.frame-benchmarking]
|
||||
optional = true
|
||||
version = "2.0.0-rc3"
|
||||
default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[build-dependencies.wasm-builder-runner]
|
||||
version = "1.0.5"
|
||||
package = "substrate-wasm-builder-runner"
|
||||
@@ -209,6 +216,7 @@ std = [
|
||||
"pallet-bridge-eth-poa/std",
|
||||
"pallet-bridge-currency-exchange/std",
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-executive/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
@@ -234,3 +242,11 @@ std = [
|
||||
"pallet-timestamp/std",
|
||||
"pallet-transaction-payment/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-bridge-currency-exchange/runtime-benchmarks",
|
||||
"pallet-bridge-eth-poa/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
|
||||
@@ -19,7 +19,7 @@ use wasm_builder_runner::WasmBuilder;
|
||||
fn main() {
|
||||
WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.with_wasm_builder_from_crates("1.0.9")
|
||||
.with_wasm_builder_from_crates("1.0.11")
|
||||
.export_heap_base()
|
||||
.import_memory()
|
||||
.build()
|
||||
|
||||
@@ -573,6 +573,27 @@ impl_runtime_apis! {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl frame_benchmarking::Benchmark<Block> for Runtime {
|
||||
fn dispatch_benchmark(
|
||||
pallet: Vec<u8>,
|
||||
benchmark: Vec<u8>,
|
||||
lowest_range_values: Vec<u32>,
|
||||
highest_range_values: Vec<u32>,
|
||||
steps: Vec<u32>,
|
||||
repeat: u32,
|
||||
) -> Result<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
|
||||
use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark};
|
||||
let mut batches = Vec::<BenchmarkBatch>::new();
|
||||
let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat);
|
||||
|
||||
add_benchmark!(params, batches, b"bridge-eth-poa", BridgeEthPoA);
|
||||
|
||||
if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
|
||||
Ok(batches)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -35,6 +35,13 @@ default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[dependencies.frame-benchmarking]
|
||||
optional = true
|
||||
version = "2.0.0-rc3"
|
||||
default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[dev-dependencies.sp-core]
|
||||
version = "2.0.0-rc3"
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
@@ -48,11 +55,13 @@ git = "https://github.com/paritytech/substrate/"
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"sp-runtime/std",
|
||||
"serde",
|
||||
"sp-currency-exchange/std",
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = ["frame-benchmarking"]
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
hex-literal = "0.2"
|
||||
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", default-features = false }
|
||||
|
||||
# Substrate Based Dependencies
|
||||
@@ -41,21 +42,40 @@ default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[dependencies.frame-benchmarking]
|
||||
optional = true
|
||||
version = "2.0.0-rc3"
|
||||
default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate/"
|
||||
|
||||
[dependencies.libsecp256k1]
|
||||
optional = true
|
||||
version = "0.3.4"
|
||||
default-features = false
|
||||
features = ["hmac"]
|
||||
|
||||
# Dev Dependencies
|
||||
[dev-dependencies]
|
||||
# TODO: Stop renaming this on import
|
||||
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", features = ["std", "test-helpers"] }
|
||||
parity-crypto = { version = "0.6", features = ["publickey"] }
|
||||
libsecp256k1 = { version = "0.3.4", features = ["hmac"] }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"frame-system/std",
|
||||
"sp-io/std",
|
||||
"primitives/std",
|
||||
"serde",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"libsecp256k1",
|
||||
"primitives/test-helpers",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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, validator_utils::*};
|
||||
|
||||
use frame_benchmarking::benchmarks;
|
||||
use frame_system::RawOrigin;
|
||||
use primitives::U256;
|
||||
|
||||
benchmarks! {
|
||||
_ { }
|
||||
|
||||
// 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;
|
||||
|
||||
// initialize storage with some initial header
|
||||
let initial_header = build_genesis_header(&validator(0));
|
||||
let initial_header_hash = initial_header.compute_hash();
|
||||
let initial_difficulty = initial_header.difficulty;
|
||||
initialize_storage::<T>(
|
||||
&initial_header,
|
||||
initial_difficulty,
|
||||
&validators_addresses(2),
|
||||
);
|
||||
|
||||
// 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 {
|
||||
assert_eq!(BridgeStorage::<T>::new().best_block().0.number, 1);
|
||||
}
|
||||
}
|
||||
@@ -282,13 +282,15 @@ impl<Submitter> Default for FinalityVotes<Submitter> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{custom_test_ext, genesis, insert_header, validator, validators_addresses, TestRuntime};
|
||||
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() {
|
||||
custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&BridgeStorage::<TestRuntime>::new(),
|
||||
@@ -306,21 +308,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn finalize_blocks_works() {
|
||||
custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| {
|
||||
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 = Header {
|
||||
author: validator(0).address().as_fixed_bytes().into(),
|
||||
parent_hash: genesis().compute_hash(),
|
||||
number: 1,
|
||||
..Default::default()
|
||||
};
|
||||
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, &genesis().compute_hash()).unwrap(),
|
||||
context: storage.import_context(None, &header1.parent_hash).unwrap(),
|
||||
is_best: true,
|
||||
id: id1,
|
||||
header: header1,
|
||||
@@ -332,8 +329,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
genesis().compute_id(),
|
||||
(Default::default(), &validators_addresses(5)),
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id1,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
@@ -345,19 +342,14 @@ mod tests {
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#2 is inserted, nothing is finalized (2 votes)
|
||||
header_to_import.header = Header {
|
||||
author: validator(1).address().as_fixed_bytes().into(),
|
||||
parent_hash: id1.hash,
|
||||
number: 2,
|
||||
..Default::default()
|
||||
};
|
||||
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,
|
||||
genesis().compute_id(),
|
||||
(Default::default(), &validators_addresses(5)),
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id2,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
@@ -369,19 +361,14 @@ mod tests {
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#3 is inserted, header#1 is finalized (3 votes)
|
||||
header_to_import.header = Header {
|
||||
author: validator(2).address().as_fixed_bytes().into(),
|
||||
parent_hash: id2.hash,
|
||||
number: 3,
|
||||
..Default::default()
|
||||
};
|
||||
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,
|
||||
genesis().compute_id(),
|
||||
(Default::default(), &validators_addresses(5)),
|
||||
ctx.genesis.compute_id(),
|
||||
(Default::default(), &ctx.addresses),
|
||||
id3,
|
||||
None,
|
||||
&header_to_import.header,
|
||||
@@ -404,11 +391,7 @@ mod tests {
|
||||
// 2) add votes from header#4 and header#5
|
||||
let validators = validators_addresses(5);
|
||||
let headers = (1..6)
|
||||
.map(|number| Header {
|
||||
number: number,
|
||||
author: validators[number as usize - 1],
|
||||
..Default::default()
|
||||
})
|
||||
.map(|number| HeaderBuilder::with_number(number).sign_by(&validator(number as usize - 1)))
|
||||
.collect::<Vec<_>>();
|
||||
let ancestry = headers
|
||||
.iter()
|
||||
@@ -451,8 +434,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prepare_votes_respects_finality_cache() {
|
||||
let validators_addresses = validators_addresses(5);
|
||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
// we need signatures of 3 validators to finalize block
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
@@ -462,14 +444,9 @@ mod tests {
|
||||
let mut hashes = Vec::new();
|
||||
let mut headers = Vec::new();
|
||||
let mut ancestry = Vec::new();
|
||||
let mut parent_hash = genesis().compute_hash();
|
||||
let mut parent_hash = ctx.genesis.compute_hash();
|
||||
for i in 1..10 {
|
||||
let header = Header {
|
||||
author: validator((i - 1) / 3).address().as_fixed_bytes().into(),
|
||||
parent_hash,
|
||||
number: i as _,
|
||||
..Default::default()
|
||||
};
|
||||
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);
|
||||
@@ -486,9 +463,9 @@ mod tests {
|
||||
// check that votes at #7 are computed correctly without cache
|
||||
let expected_votes_at_7 = FinalityVotes {
|
||||
votes: vec![
|
||||
(validators_addresses[0].clone(), 3),
|
||||
(validators_addresses[1].clone(), 3),
|
||||
(validators_addresses[2].clone(), 1),
|
||||
(ctx.addresses[0].clone(), 3),
|
||||
(ctx.addresses[1].clone(), 3),
|
||||
(ctx.addresses[2].clone(), 1),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
@@ -499,11 +476,11 @@ mod tests {
|
||||
prepare_votes(
|
||||
storage.cached_finality_votes(
|
||||
&headers.get(5).unwrap().compute_id(),
|
||||
&genesis().compute_id(),
|
||||
&ctx.genesis.compute_id(),
|
||||
|_| false,
|
||||
),
|
||||
Default::default(),
|
||||
&validators_addresses.iter().collect(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
@@ -514,12 +491,9 @@ mod tests {
|
||||
|
||||
// cached votes at #5
|
||||
let expected_votes_at_5 = FinalityVotes {
|
||||
votes: vec![
|
||||
(validators_addresses[0].clone(), 3),
|
||||
(validators_addresses[1].clone(), 2),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
votes: vec![(ctx.addresses[0].clone(), 3), (ctx.addresses[1].clone(), 2)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
ancestry: ancestry[..5].iter().cloned().collect(),
|
||||
};
|
||||
FinalityCache::<TestRuntime>::insert(hashes[4], expected_votes_at_5);
|
||||
@@ -530,11 +504,11 @@ mod tests {
|
||||
prepare_votes(
|
||||
storage.cached_finality_votes(
|
||||
&headers.get(5).unwrap().compute_id(),
|
||||
&genesis().compute_id(),
|
||||
&ctx.genesis.compute_id(),
|
||||
|_| false,
|
||||
),
|
||||
Default::default(),
|
||||
&validators_addresses.iter().collect(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
@@ -546,12 +520,9 @@ mod tests {
|
||||
// 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![
|
||||
(validators_addresses[1].clone(), 3),
|
||||
(validators_addresses[2].clone(), 1),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
votes: vec![(ctx.addresses[1].clone(), 3), (ctx.addresses[2].clone(), 1)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
ancestry: ancestry[3..7].iter().cloned().collect(),
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -562,7 +533,7 @@ mod tests {
|
||||
|hash| *hash == hashes[2],
|
||||
),
|
||||
headers[2].compute_id(),
|
||||
&validators_addresses.iter().collect(),
|
||||
&ctx.addresses.iter().collect(),
|
||||
id7,
|
||||
headers.get(6).unwrap(),
|
||||
None,
|
||||
|
||||
@@ -162,17 +162,19 @@ pub fn header_import_requires_receipts<S: Storage>(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
block_i, custom_block_i, custom_test_ext, genesis, signed_header, test_aura_config, test_validators_config,
|
||||
validator, validators, validators_addresses, KeepSomeHeadersBehindBest, TestRuntime, GENESIS_STEP,
|
||||
run_test, secret_to_address, test_aura_config, test_validators_config, validator, validators_addresses,
|
||||
HeaderBuilder, KeepSomeHeadersBehindBest, TestRuntime, GAS_LIMIT,
|
||||
};
|
||||
use crate::validators::ValidatorsSource;
|
||||
use crate::{BlocksToPrune, BridgeStorage, Headers, PruningRange};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use parity_crypto::publickey::KeyPair;
|
||||
use secp256k1::SecretKey;
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
#[test]
|
||||
fn rejects_finalized_block_competitors() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
storage.finalize_and_prune_headers(
|
||||
Some(HeaderId {
|
||||
@@ -198,10 +200,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rejects_known_header() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
let validators = validators(3);
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let block = block_i(1, &validators);
|
||||
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
@@ -209,7 +210,7 @@ mod tests {
|
||||
&test_aura_config(),
|
||||
&test_validators_config(),
|
||||
None,
|
||||
block.clone(),
|
||||
header.clone(),
|
||||
None,
|
||||
)
|
||||
.map(|_| ()),
|
||||
@@ -222,7 +223,7 @@ mod tests {
|
||||
&test_aura_config(),
|
||||
&test_validators_config(),
|
||||
None,
|
||||
block,
|
||||
header,
|
||||
None,
|
||||
)
|
||||
.map(|_| ()),
|
||||
@@ -233,14 +234,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn import_header_works() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let validators_config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(validators_addresses(3))),
|
||||
(0, ValidatorsSource::List(ctx.addresses.clone())),
|
||||
(1, ValidatorsSource::List(validators_addresses(2))),
|
||||
]);
|
||||
let validators = validators(3);
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let header = block_i(1, &validators);
|
||||
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
|
||||
let hash = header.compute_hash();
|
||||
assert_eq!(
|
||||
import_header(
|
||||
@@ -267,9 +267,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn headers_are_pruned_during_import() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let validators_config =
|
||||
ValidatorsConfiguration::Single(ValidatorsSource::Contract([3; 20].into(), validators_addresses(3)));
|
||||
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();
|
||||
|
||||
@@ -277,7 +277,9 @@ mod tests {
|
||||
// => 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 = block_i(i, &validators);
|
||||
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(),
|
||||
@@ -289,26 +291,24 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
match i {
|
||||
2..=10 => assert_eq!(
|
||||
finalized_blocks,
|
||||
vec![(block_i(i - 1, &validators).compute_id(), Some(100))],
|
||||
"At {}",
|
||||
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(&genesis().compute_hash()).is_some());
|
||||
assert!(storage.header(&ctx.genesis.compute_hash()).is_some());
|
||||
|
||||
// header 11 finalizes headers [10] AND schedules change
|
||||
// => we prune header#0
|
||||
let header11 = custom_block_i(11, &validators, |header| {
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951"
|
||||
.parse()
|
||||
.unwrap();
|
||||
});
|
||||
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(),
|
||||
@@ -321,29 +321,20 @@ mod tests {
|
||||
)]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
finalized_blocks,
|
||||
vec![(block_i(10, &validators).compute_id(), Some(100))],
|
||||
);
|
||||
assert!(storage.header(&genesis().compute_hash()).is_none());
|
||||
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 = 56;
|
||||
let mut step = 56u64;
|
||||
let mut expected_blocks = vec![(header11.compute_id(), Some(101))];
|
||||
for i in 12..25 {
|
||||
let header = Header {
|
||||
number: i as _,
|
||||
parent_hash: latest_block_id.hash,
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(2).address(),
|
||||
seal: vec![vec![step].into(), vec![].into()],
|
||||
difficulty: i.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let header = signed_header(&validators, header, step as _);
|
||||
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,
|
||||
@@ -370,16 +361,10 @@ mod tests {
|
||||
// now let's insert block signed by validator 1
|
||||
// => blocks 11..24 are finalized and blocks 11..14 are pruned
|
||||
step -= 2;
|
||||
let header = Header {
|
||||
number: 25,
|
||||
parent_hash: latest_block_id.hash,
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(0).address(),
|
||||
seal: vec![vec![step].into(), vec![].into()],
|
||||
difficulty: 25.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let header = signed_header(&validators, header, step as _);
|
||||
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(),
|
||||
@@ -403,18 +388,9 @@ mod tests {
|
||||
|
||||
fn import_custom_block<S: Storage>(
|
||||
storage: &mut S,
|
||||
validators: &[KeyPair],
|
||||
number: u64,
|
||||
step: u64,
|
||||
customize: impl FnOnce(&mut Header),
|
||||
validators: &[SecretKey],
|
||||
header: Header,
|
||||
) -> Result<HeaderId, Error> {
|
||||
let header = custom_block_i(number, validators, |header| {
|
||||
header.seal[0][0] = step as _;
|
||||
header.author =
|
||||
crate::validators::step_validator(&validators.iter().map(|kp| kp.address()).collect::<Vec<_>>(), step);
|
||||
customize(header);
|
||||
});
|
||||
let header = signed_header(validators, header, step);
|
||||
let id = header.compute_id();
|
||||
import_header(
|
||||
storage,
|
||||
@@ -422,7 +398,7 @@ mod tests {
|
||||
&test_aura_config(),
|
||||
&ValidatorsConfiguration::Single(ValidatorsSource::Contract(
|
||||
[0; 20].into(),
|
||||
validators.iter().map(|kp| kp.address()).collect(),
|
||||
validators.iter().map(secret_to_address).collect(),
|
||||
)),
|
||||
None,
|
||||
header,
|
||||
@@ -433,37 +409,41 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn import_of_non_best_block_may_finalize_blocks() {
|
||||
const TOTAL_VALIDATORS: u8 = 3;
|
||||
let validators_addresses = validators_addresses(TOTAL_VALIDATORS);
|
||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
||||
let validators = validators(TOTAL_VALIDATORS);
|
||||
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 = GENESIS_STEP + i * TOTAL_VALIDATORS as u64;
|
||||
expected_best_block = import_custom_block(&mut storage, &validators, i, step, |header| {
|
||||
header.author = validators_addresses[0];
|
||||
header.seal[0][0] = step as u8;
|
||||
})
|
||||
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(), genesis().compute_id());
|
||||
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 = genesis().compute_hash();
|
||||
let mut parent_hash = ctx.genesis.compute_hash();
|
||||
for i in 1..3 {
|
||||
let step = GENESIS_STEP + i;
|
||||
let id = import_custom_block(&mut storage, &validators, i, step, |header| {
|
||||
header.gas_limit += 1.into();
|
||||
header.parent_hash = parent_hash;
|
||||
})
|
||||
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 {
|
||||
@@ -479,83 +459,117 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn append_to_unfinalized_fork_fails() {
|
||||
const TOTAL_VALIDATORS: u64 = 5;
|
||||
let validators_addresses = validators_addresses(TOTAL_VALIDATORS as _);
|
||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
||||
let validators = validators(TOTAL_VALIDATORS as _);
|
||||
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, &validators, 1, GENESIS_STEP + 1, |_| ()).unwrap();
|
||||
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, &validators, 2, GENESIS_STEP + 2, |_| ()).unwrap();
|
||||
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, &validators, 3, GENESIS_STEP + 3, |_| ()).unwrap();
|
||||
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, &validators, 2, GENESIS_STEP + 3, |header| {
|
||||
header.gas_limit += 1.into();
|
||||
})
|
||||
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,
|
||||
&validators,
|
||||
3,
|
||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS,
|
||||
|header| {
|
||||
header.parent_hash = header2_1.hash;
|
||||
},
|
||||
&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,
|
||||
&validators,
|
||||
4,
|
||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 2,
|
||||
|header| {
|
||||
header.parent_hash = header3_1.hash;
|
||||
},
|
||||
&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,
|
||||
&validators,
|
||||
5,
|
||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 3,
|
||||
|header| {
|
||||
header.parent_hash = header4_1.hash;
|
||||
},
|
||||
&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, &validators, 4, GENESIS_STEP + 4, |_| ()).unwrap();
|
||||
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 _ = import_custom_block(&mut storage, &validators, 5, GENESIS_STEP + 5, |header| {
|
||||
header.parent_hash = header4.hash;
|
||||
})
|
||||
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_1);
|
||||
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, &validators, 2, GENESIS_STEP + 3, |header| {
|
||||
header.gas_limit += 2.into();
|
||||
}),
|
||||
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),
|
||||
);
|
||||
|
||||
@@ -564,10 +578,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
import_custom_block(
|
||||
&mut storage,
|
||||
&validators,
|
||||
6,
|
||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 4,
|
||||
|_| ()
|
||||
&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),
|
||||
);
|
||||
|
||||
@@ -37,9 +37,15 @@ mod import;
|
||||
mod validators;
|
||||
mod verification;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
mod test_utils;
|
||||
|
||||
/// Maximal number of blocks we're pruning in single import call.
|
||||
const MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT: u64 = 8;
|
||||
|
||||
@@ -464,32 +470,11 @@ decl_storage! {
|
||||
"Initial validators set can't be empty",
|
||||
);
|
||||
|
||||
let initial_hash = config.initial_header.compute_hash();
|
||||
let initial_id = HeaderId {
|
||||
number: config.initial_header.number,
|
||||
hash: initial_hash,
|
||||
};
|
||||
BestBlock::put((initial_id, config.initial_difficulty));
|
||||
FinalizedBlock::put(initial_id);
|
||||
BlocksToPrune::put(PruningRange {
|
||||
oldest_unpruned_block: config.initial_header.number,
|
||||
oldest_block_to_keep: config.initial_header.number,
|
||||
});
|
||||
HeadersByNumber::insert(config.initial_header.number, vec![initial_hash]);
|
||||
Headers::<T>::insert(initial_hash, StoredHeader {
|
||||
submitter: None,
|
||||
header: config.initial_header.clone(),
|
||||
total_difficulty: config.initial_difficulty,
|
||||
next_validators_set_id: 0,
|
||||
last_signal_block: None,
|
||||
});
|
||||
NextValidatorsSetId::put(1);
|
||||
ValidatorsSets::insert(0, ValidatorsSet {
|
||||
validators: config.initial_validators.clone(),
|
||||
signal_block: None,
|
||||
enact_block: initial_id,
|
||||
});
|
||||
ValidatorsSetsRc::insert(0, 1);
|
||||
initialize_storage::<T>(
|
||||
&config.initial_header,
|
||||
config.initial_difficulty,
|
||||
&config.initial_validators,
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -632,6 +617,13 @@ impl<T: Trait> BridgeStorage<T> {
|
||||
// physically remove headers and (probably) obsolete validators sets
|
||||
while let Some(hash) = blocks_at_number.pop() {
|
||||
let header = Headers::<T>::take(&hash);
|
||||
frame_support::debug::trace!(
|
||||
target: "runtime",
|
||||
"Pruning PoA header: ({}, {})",
|
||||
number,
|
||||
hash,
|
||||
);
|
||||
|
||||
ScheduledChanges::remove(hash);
|
||||
FinalityCache::<T>::remove(hash);
|
||||
if let Some(header) = header {
|
||||
@@ -830,6 +822,53 @@ impl<T: Trait> Storage for BridgeStorage<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize storage.
|
||||
pub(crate) fn initialize_storage<T: Trait>(
|
||||
initial_header: &Header,
|
||||
initial_difficulty: U256,
|
||||
initial_validators: &[Address],
|
||||
) {
|
||||
let initial_hash = initial_header.compute_hash();
|
||||
frame_support::debug::trace!(
|
||||
target: "runtime",
|
||||
"Initializing bridge with PoA header: ({}, {})",
|
||||
initial_header.number,
|
||||
initial_hash,
|
||||
);
|
||||
|
||||
let initial_id = HeaderId {
|
||||
number: initial_header.number,
|
||||
hash: initial_hash,
|
||||
};
|
||||
BestBlock::put((initial_id, initial_difficulty));
|
||||
FinalizedBlock::put(initial_id);
|
||||
BlocksToPrune::put(PruningRange {
|
||||
oldest_unpruned_block: initial_header.number,
|
||||
oldest_block_to_keep: initial_header.number,
|
||||
});
|
||||
HeadersByNumber::insert(initial_header.number, vec![initial_hash]);
|
||||
Headers::<T>::insert(
|
||||
initial_hash,
|
||||
StoredHeader {
|
||||
submitter: None,
|
||||
header: initial_header.clone(),
|
||||
total_difficulty: initial_difficulty,
|
||||
next_validators_set_id: 0,
|
||||
last_signal_block: None,
|
||||
},
|
||||
);
|
||||
NextValidatorsSetId::put(1);
|
||||
ValidatorsSets::insert(
|
||||
0,
|
||||
ValidatorsSet {
|
||||
validators: initial_validators.to_vec(),
|
||||
signal_block: None,
|
||||
enact_block: initial_id,
|
||||
},
|
||||
);
|
||||
ValidatorsSetsRc::insert(0, 1);
|
||||
}
|
||||
|
||||
/// Verify that transaction is included into given finalized block.
|
||||
pub fn verify_transaction_finalized<S: Storage>(
|
||||
storage: &S,
|
||||
@@ -894,10 +933,13 @@ pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::finality::FinalityAncestor;
|
||||
use crate::mock::{
|
||||
block_i, custom_block_i, custom_test_ext, genesis, insert_header, validators, validators_addresses, TestRuntime,
|
||||
genesis, insert_header, run_test, run_test_with_genesis, validators_addresses, HeaderBuilder, TestRuntime,
|
||||
GAS_LIMIT,
|
||||
};
|
||||
use primitives::compute_merkle_root;
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
fn example_tx() -> Vec<u8> {
|
||||
vec![42]
|
||||
}
|
||||
@@ -919,14 +961,13 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn with_headers_to_prune<T>(f: impl Fn(BridgeStorage<TestRuntime>) -> T) -> T {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
let validators = validators(3);
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
for i in 1..10 {
|
||||
let mut headers_by_number = Vec::with_capacity(5);
|
||||
for j in 0..5 {
|
||||
let header = custom_block_i(i, &validators, |header| {
|
||||
header.gas_limit = header.gas_limit + U256::from(j);
|
||||
});
|
||||
let header = HeaderBuilder::with_parent_number(i - 1)
|
||||
.gas_limit((GAS_LIMIT + j).into())
|
||||
.sign_by_set(&ctx.validators);
|
||||
let hash = header.compute_hash();
|
||||
headers_by_number.push(hash);
|
||||
Headers::<TestRuntime>::insert(
|
||||
@@ -1082,21 +1123,20 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn finality_votes_are_cached() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let interval = <TestRuntime as Trait>::FinalityVotesCachingInterval::get().unwrap();
|
||||
|
||||
// for all headers with number < interval, cache entry is not created
|
||||
let validators = validators(3);
|
||||
for i in 1..interval {
|
||||
let header = block_i(i, &validators);
|
||||
let header = HeaderBuilder::with_parent_number(i - 1).sign_by_set(&ctx.validators);
|
||||
let id = header.compute_id();
|
||||
insert_header(&mut storage, header);
|
||||
assert_eq!(FinalityCache::<TestRuntime>::get(&id.hash), None);
|
||||
}
|
||||
|
||||
// for header with number = interval, cache entry is created
|
||||
let header_with_entry = block_i(interval, &validators);
|
||||
let header_with_entry = HeaderBuilder::with_parent_number(interval - 1).sign_by_set(&ctx.validators);
|
||||
let header_with_entry_hash = header_with_entry.compute_hash();
|
||||
insert_header(&mut storage, header_with_entry);
|
||||
assert_eq!(
|
||||
@@ -1116,13 +1156,12 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn cached_finality_votes_finds_entry() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
// insert 5 headers
|
||||
let validators = validators(3);
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let mut headers = Vec::new();
|
||||
for i in 1..5 {
|
||||
let header = block_i(i, &validators);
|
||||
let header = HeaderBuilder::with_parent_number(i - 1).sign_by_set(&ctx.validators);
|
||||
headers.push(header.clone());
|
||||
insert_header(&mut storage, header);
|
||||
}
|
||||
@@ -1177,19 +1216,18 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn cached_finality_votes_stops_at_finalized_sibling() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
let validators = validators(3);
|
||||
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
// insert header1
|
||||
let header1 = block_i(1, &validators);
|
||||
let header1 = HeaderBuilder::with_parent_number(0).sign_by_set(&ctx.validators);
|
||||
let header1_id = header1.compute_id();
|
||||
insert_header(&mut storage, header1);
|
||||
|
||||
// insert header1' - sibling of header1
|
||||
let header1s = custom_block_i(1, &validators, |header| {
|
||||
header.gas_limit += 1.into();
|
||||
});
|
||||
let header1s = HeaderBuilder::with_parent_number(0)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.sign_by_set(&ctx.validators);
|
||||
let header1s_id = header1s.compute_id();
|
||||
insert_header(&mut storage, header1s);
|
||||
|
||||
@@ -1214,7 +1252,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_works_for_best_finalized_header() {
|
||||
custom_test_ext(example_header(), validators_addresses(3)).execute_with(|| {
|
||||
run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
assert_eq!(
|
||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &vec![example_tx()],),
|
||||
@@ -1225,7 +1263,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_works_for_best_finalized_header_ancestor() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
insert_header(&mut storage, example_header_parent());
|
||||
insert_header(&mut storage, example_header());
|
||||
@@ -1239,7 +1277,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_proof_with_missing_tx() {
|
||||
custom_test_ext(example_header(), validators_addresses(3)).execute_with(|| {
|
||||
run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
assert_eq!(
|
||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
||||
@@ -1250,7 +1288,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_unknown_header() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
assert_eq!(
|
||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
||||
@@ -1261,7 +1299,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_unfinalized_header() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
insert_header(&mut storage, example_header_parent());
|
||||
insert_header(&mut storage, example_header());
|
||||
@@ -1274,7 +1312,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_finalized_header_sibling() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut finalized_header_sibling = example_header();
|
||||
finalized_header_sibling.timestamp = 1;
|
||||
let finalized_header_sibling_hash = finalized_header_sibling.compute_hash();
|
||||
@@ -1293,7 +1331,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_finalized_header_uncle() {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let mut finalized_header_uncle = example_header_parent();
|
||||
finalized_header_uncle.timestamp = 1;
|
||||
let finalized_header_uncle_hash = finalized_header_uncle.compute_hash();
|
||||
@@ -1312,7 +1350,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn verify_transaction_finalized_rejects_invalid_proof() {
|
||||
custom_test_ext(example_header(), validators_addresses(3)).execute_with(|| {
|
||||
run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
assert_eq!(
|
||||
verify_transaction_finalized(
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
// 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/>.
|
||||
|
||||
pub use crate::test_utils::{validator_utils::*, HeaderBuilder, GAS_LIMIT};
|
||||
pub use primitives::signatures::secret_to_address;
|
||||
|
||||
use crate::finality::FinalityVotes;
|
||||
use crate::validators::{ValidatorsConfiguration, ValidatorsSource};
|
||||
use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, HeadersByNumber, PruningStrategy, Storage, Trait};
|
||||
use frame_support::StorageMap;
|
||||
use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, PruningStrategy, Storage, Trait};
|
||||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
|
||||
use parity_crypto::publickey::{sign, KeyPair, Secret};
|
||||
use primitives::{rlp_encode, H520};
|
||||
use primitives::{Address, Header, H256, U256};
|
||||
use secp256k1::SecretKey;
|
||||
use sp_runtime::{
|
||||
testing::Header as SubstrateHeader,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
@@ -84,8 +85,17 @@ impl Trait for TestRuntime {
|
||||
type OnHeadersSubmitted = ();
|
||||
}
|
||||
|
||||
/// Step of genesis header.
|
||||
pub const GENESIS_STEP: u64 = 42;
|
||||
/// Test context.
|
||||
pub struct TestContext {
|
||||
/// Initial (genesis) header.
|
||||
pub genesis: Header,
|
||||
/// 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 {
|
||||
@@ -108,72 +118,38 @@ pub fn test_validators_config() -> ValidatorsConfiguration {
|
||||
|
||||
/// Genesis header that is used in tests by default.
|
||||
pub fn genesis() -> Header {
|
||||
Header {
|
||||
seal: vec![vec![GENESIS_STEP as _].into(), vec![].into()],
|
||||
..Default::default()
|
||||
}
|
||||
HeaderBuilder::genesis().sign_by(&validator(0))
|
||||
}
|
||||
|
||||
/// Build default i-th block, using data from runtime storage.
|
||||
pub fn block_i(number: u64, validators: &[KeyPair]) -> Header {
|
||||
custom_block_i(number, validators, |_| {})
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Build custom i-th block, using data from runtime storage.
|
||||
pub fn custom_block_i(number: u64, validators: &[KeyPair], customize: impl FnOnce(&mut Header)) -> Header {
|
||||
let validator_index: u8 = (number % (validators.len() as u64)) as _;
|
||||
let mut header = Header {
|
||||
number,
|
||||
parent_hash: HeadersByNumber::get(number - 1).unwrap()[0].clone(),
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(validator_index).address(),
|
||||
seal: vec![vec![(number + GENESIS_STEP) as u8].into(), vec![].into()],
|
||||
difficulty: number.into(),
|
||||
..Default::default()
|
||||
};
|
||||
customize(&mut header);
|
||||
signed_header(validators, header, number + GENESIS_STEP)
|
||||
/// Run test with default genesis header.
|
||||
pub fn run_test_with_genesis<T>(genesis: Header, total_validators: usize, test: impl FnOnce(TestContext) -> T) -> T {
|
||||
let validators = validators(total_validators);
|
||||
let addresses = validators_addresses(total_validators);
|
||||
sp_io::TestExternalities::new(
|
||||
GenesisConfig {
|
||||
initial_header: genesis.clone(),
|
||||
initial_difficulty: 0.into(),
|
||||
initial_validators: addresses.clone(),
|
||||
}
|
||||
.build_storage::<TestRuntime>()
|
||||
.unwrap(),
|
||||
)
|
||||
.execute_with(|| {
|
||||
test(TestContext {
|
||||
genesis,
|
||||
total_validators,
|
||||
validators,
|
||||
addresses,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Build signed header from given header.
|
||||
pub fn signed_header(validators: &[KeyPair], mut header: Header, step: u64) -> Header {
|
||||
let message = header.seal_hash(false).unwrap();
|
||||
let validator_index = (step % validators.len() as u64) as usize;
|
||||
let signature = sign(validators[validator_index].secret(), &message.as_fixed_bytes().into()).unwrap();
|
||||
let signature: [u8; 65] = signature.into();
|
||||
let signature = H520::from(signature);
|
||||
header.seal[1] = rlp_encode(&signature);
|
||||
header
|
||||
}
|
||||
|
||||
/// Return key pair of given test validator.
|
||||
pub fn validator(index: u8) -> KeyPair {
|
||||
KeyPair::from_secret(Secret::from([index + 1; 32])).unwrap()
|
||||
}
|
||||
|
||||
/// Return key pairs of all test validators.
|
||||
pub fn validators(count: u8) -> Vec<KeyPair> {
|
||||
(0..count).map(validator).collect()
|
||||
}
|
||||
|
||||
/// Return addresses of all test validators.
|
||||
pub fn validators_addresses(count: u8) -> Vec<Address> {
|
||||
(0..count).map(|i| validator(i).address()).collect()
|
||||
}
|
||||
|
||||
/// Prepare externalities to start with custom initial header.
|
||||
pub fn custom_test_ext(initial_header: Header, initial_validators: Vec<Address>) -> sp_io::TestExternalities {
|
||||
let t = GenesisConfig {
|
||||
initial_header,
|
||||
initial_difficulty: 0.into(),
|
||||
initial_validators,
|
||||
}
|
||||
.build_storage::<TestRuntime>()
|
||||
.unwrap();
|
||||
sp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
/// Insert header into storage.
|
||||
/// Insert unverified header into storage.
|
||||
pub fn insert_header<S: Storage>(storage: &mut S, header: Header) {
|
||||
storage.insert_header(HeaderToImport {
|
||||
context: storage.import_context(None, &header.parent_hash).unwrap(),
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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.
|
||||
|
||||
use crate::verification::calculate_score;
|
||||
|
||||
use primitives::{
|
||||
rlp_encode,
|
||||
signatures::{secret_to_address, sign, SignHeader},
|
||||
Address, Bloom, Header, 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: Header,
|
||||
parent_header: Header,
|
||||
}
|
||||
|
||||
impl HeaderBuilder {
|
||||
/// Creates default genesis header.
|
||||
pub fn genesis() -> Self {
|
||||
let current_step = 0u64;
|
||||
Self {
|
||||
header: Header {
|
||||
gas_limit: GAS_LIMIT.into(),
|
||||
seal: vec![primitives::rlp_encode(¤t_step), vec![]],
|
||||
..Default::default()
|
||||
},
|
||||
parent_header: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates default header on top of parent with given hash.
|
||||
#[cfg(test)]
|
||||
pub fn with_parent_hash(parent_hash: H256) -> Self {
|
||||
use crate::mock::TestRuntime;
|
||||
use crate::Headers;
|
||||
use frame_support::StorageMap;
|
||||
|
||||
let parent_header = Headers::<TestRuntime>::get(&parent_hash).unwrap().header;
|
||||
Self::with_parent(&parent_header)
|
||||
}
|
||||
|
||||
/// Creates default header on top of parent with given number. First parent is selected.
|
||||
#[cfg(test)]
|
||||
pub fn with_parent_number(parent_number: u64) -> Self {
|
||||
use crate::HeadersByNumber;
|
||||
use frame_support::StorageMap;
|
||||
|
||||
let parent_hash = HeadersByNumber::get(parent_number).unwrap()[0].clone();
|
||||
Self::with_parent_hash(parent_hash)
|
||||
}
|
||||
|
||||
/// Creates default header on top of non-existent parent.
|
||||
#[cfg(test)]
|
||||
pub fn with_number(number: u64) -> Self {
|
||||
Self::with_parent(&Header {
|
||||
number: number - 1,
|
||||
seal: vec![primitives::rlp_encode(&(number - 1)), vec![]],
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates default header on top of given parent.
|
||||
pub fn with_parent(parent_header: &Header) -> Self {
|
||||
let parent_step = parent_header.step().unwrap();
|
||||
let current_step = parent_step + 1;
|
||||
Self {
|
||||
header: Header {
|
||||
parent_hash: parent_header.compute_hash(),
|
||||
number: parent_header.number + 1,
|
||||
gas_limit: GAS_LIMIT.into(),
|
||||
seal: vec![primitives::rlp_encode(¤t_step), 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);
|
||||
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
|
||||
.into_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
|
||||
}
|
||||
|
||||
/// Signs header by given author.
|
||||
pub fn sign_by(self, author: &SecretKey) -> Header {
|
||||
self.header.sign_by(author)
|
||||
}
|
||||
|
||||
/// Signs header by given authors set.
|
||||
pub fn sign_by_set(self, authors: &[SecretKey]) -> Header {
|
||||
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) -> Header {
|
||||
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: &Header, customize_header: F) -> Header
|
||||
where
|
||||
F: FnOnce(Header) -> Header,
|
||||
{
|
||||
let new_header = HeaderBuilder::with_parent(&previous);
|
||||
let custom_header = customize_header(new_header.header);
|
||||
custom_header.sign_by(author)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -273,19 +273,16 @@ impl ValidatorsSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get validator that should author the block at given step.
|
||||
pub fn step_validator(header_validators: &[Address], header_step: u64) -> Address {
|
||||
header_validators[(header_step % header_validators.len() as u64) as usize]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{custom_test_ext, genesis, validators_addresses, TestRuntime};
|
||||
use crate::mock::{run_test, validators_addresses, TestRuntime};
|
||||
use crate::{BridgeStorage, Headers, ScheduledChange, ScheduledChanges, StoredHeader};
|
||||
use frame_support::StorageMap;
|
||||
use primitives::{TransactionOutcome, H256};
|
||||
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt {
|
||||
Receipt {
|
||||
gas_used: 0.into(),
|
||||
@@ -425,7 +422,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn try_finalize_with_scheduled_change(scheduled_at: Option<HeaderId>) -> Option<ChangeToEnact> {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test(TOTAL_VALIDATORS, |_| {
|
||||
let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||
let validators = Validators::new(&config);
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::validators::{step_validator, Validators, ValidatorsConfiguration};
|
||||
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||
use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage};
|
||||
use codec::Encode;
|
||||
use primitives::{public_to_address, Address, Header, HeaderId, Receipt, SealedEmptyStep, H256, H520, U128, U256};
|
||||
use primitives::{
|
||||
public_to_address, step_validator, Address, Header, HeaderId, Receipt, SealedEmptyStep, H256, H520, U128, U256,
|
||||
};
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use sp_std::{vec, vec::Vec};
|
||||
|
||||
@@ -157,9 +159,16 @@ pub fn verify_aura_header<S: Storage>(
|
||||
contextless_checks(config, header)?;
|
||||
|
||||
// the rest of checks requires access to the parent header
|
||||
let context = storage
|
||||
.import_context(submitter, &header.parent_hash)
|
||||
.ok_or(Error::MissingParentBlock)?;
|
||||
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)?;
|
||||
|
||||
@@ -263,7 +272,7 @@ fn validator_checks(
|
||||
header: &Header,
|
||||
header_step: u64,
|
||||
) -> Result<(), Error> {
|
||||
let expected_validator = step_validator(validators, header_step);
|
||||
let expected_validator = *step_validator(validators, header_step);
|
||||
if header.author != expected_validator {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
@@ -282,7 +291,7 @@ fn validator_checks(
|
||||
|
||||
/// Returns expected number of seal fields in the header.
|
||||
fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> usize {
|
||||
if header.number >= config.empty_steps_transition {
|
||||
if header.number != u64::max_value() && header.number >= config.empty_steps_transition {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
@@ -291,13 +300,13 @@ fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> u
|
||||
|
||||
/// 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 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
|
||||
fn calculate_score(parent_step: u64, current_step: u64, current_empty_steps: usize) -> U256 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -344,8 +353,8 @@ fn find_next_validators_signal<S: Storage>(storage: &S, context: &ImportContext<
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
block_i, custom_block_i, custom_test_ext, genesis, insert_header, signed_header, test_aura_config, validator,
|
||||
validators_addresses, AccountId, TestRuntime,
|
||||
insert_header, run_test_with_genesis, test_aura_config, validator, validator_address, validators_addresses,
|
||||
AccountId, HeaderBuilder, TestRuntime, GAS_LIMIT,
|
||||
};
|
||||
use crate::validators::{tests::validators_change_recept, ValidatorsSource};
|
||||
use crate::{
|
||||
@@ -353,25 +362,18 @@ mod tests {
|
||||
ScheduledChanges, ValidatorsSet, ValidatorsSets,
|
||||
};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use parity_crypto::publickey::{sign, KeyPair};
|
||||
use primitives::{rlp_encode, TransactionOutcome, H520};
|
||||
use secp256k1::SecretKey;
|
||||
|
||||
fn sealed_empty_step(validators: &[KeyPair], parent_hash: &H256, step: u64) -> SealedEmptyStep {
|
||||
let mut empty_step = SealedEmptyStep {
|
||||
step,
|
||||
signature: Default::default(),
|
||||
};
|
||||
let message = empty_step.message(parent_hash);
|
||||
let validator_index = (step % validators.len() as u64) as usize;
|
||||
let signature: [u8; 65] = sign(validators[validator_index].secret(), &message.as_fixed_bytes().into())
|
||||
.unwrap()
|
||||
.into();
|
||||
empty_step.signature = signature.into();
|
||||
empty_step
|
||||
const GENESIS_STEP: u64 = 42;
|
||||
const TOTAL_VALIDATORS: usize = 3;
|
||||
|
||||
fn genesis() -> Header {
|
||||
HeaderBuilder::genesis().step(GENESIS_STEP).sign_by(&validator(0))
|
||||
}
|
||||
|
||||
fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result<ImportContext<AccountId>, Error> {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
|
||||
let storage = BridgeStorage::<TestRuntime>::new();
|
||||
verify_aura_header(&storage, &config, None, header)
|
||||
})
|
||||
@@ -382,17 +384,17 @@ mod tests {
|
||||
}
|
||||
|
||||
fn default_accept_into_pool(
|
||||
mut make_header: impl FnMut(&[KeyPair]) -> (Header, Option<Vec<Receipt>>),
|
||||
mut make_header: impl FnMut(&[SecretKey]) -> (Header, Option<Vec<Receipt>>),
|
||||
) -> Result<(Vec<Vec<u8>>, Vec<Vec<u8>>), Error> {
|
||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
||||
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||
let block1 = block_i(1, &validators);
|
||||
let block1 = HeaderBuilder::with_parent_number(0).sign_by_set(&validators);
|
||||
insert_header(&mut storage, block1);
|
||||
let block2 = block_i(2, &validators);
|
||||
let block2 = HeaderBuilder::with_parent_number(1).sign_by_set(&validators);
|
||||
let block2_id = block2.compute_id();
|
||||
insert_header(&mut storage, block2);
|
||||
let block3 = block_i(3, &validators);
|
||||
let block3 = HeaderBuilder::with_parent_number(2).sign_by_set(&validators);
|
||||
insert_header(&mut storage, block3);
|
||||
|
||||
FinalizedBlock::put(block2_id);
|
||||
@@ -468,32 +470,26 @@ mod tests {
|
||||
#[test]
|
||||
fn verifies_header_number() {
|
||||
// when number is u64::max_value()
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into(), vec![].into()],
|
||||
number: u64::max_value(),
|
||||
..Default::default()
|
||||
};
|
||||
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()
|
||||
header.seal = vec![vec![].into(), vec![].into()];
|
||||
header.number -= 1;
|
||||
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 mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_used: 1.into(),
|
||||
gas_limit: 0.into(),
|
||||
..Default::default()
|
||||
};
|
||||
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
|
||||
header.gas_limit = 1.into();
|
||||
let header = HeaderBuilder::with_number(1)
|
||||
.gas_used((GAS_LIMIT - 1).into())
|
||||
.sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||
}
|
||||
|
||||
@@ -504,67 +500,62 @@ mod tests {
|
||||
config.max_gas_limit = 200.into();
|
||||
|
||||
// when limit is lower than expected
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: 50.into(),
|
||||
..Default::default()
|
||||
};
|
||||
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
|
||||
header.gas_limit = 250.into();
|
||||
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
|
||||
header.gas_limit = 150.into();
|
||||
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 mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
extra_data: std::iter::repeat(42).take(1000).collect::<Vec<_>>().into(),
|
||||
number: 1,
|
||||
..Default::default()
|
||||
};
|
||||
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
|
||||
header.extra_data = std::iter::repeat(42).take(10).collect::<Vec<_>>().into();
|
||||
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 mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
timestamp: i32::max_value() as u64 + 1,
|
||||
..Default::default()
|
||||
};
|
||||
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
|
||||
header.timestamp -= 1;
|
||||
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_parent_existence() {
|
||||
// when there's no parent in the storage
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
..Default::default()
|
||||
};
|
||||
let header = HeaderBuilder::with_number(1).sign_by(&validator(0));
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
|
||||
// when parent is in the storage
|
||||
header.parent_hash = genesis().compute_hash();
|
||||
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
|
||||
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
}
|
||||
|
||||
@@ -580,11 +571,11 @@ mod tests {
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingStep));
|
||||
|
||||
// when step is the same as for the parent block
|
||||
header.seal = vec![vec![42].into(), vec![].into()];
|
||||
header.seal[0] = rlp_encode(&42u64);
|
||||
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal = vec![vec![43].into(), vec![].into()];
|
||||
header.seal[0] = rlp_encode(&43u64);
|
||||
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// now check with validate_step check enabled
|
||||
@@ -592,52 +583,47 @@ mod tests {
|
||||
config.validate_step_transition = 0;
|
||||
|
||||
// when step is lesser that for the parent block
|
||||
header.seal[0] = rlp_encode(&40u64);
|
||||
header.seal = vec![vec![40].into(), vec![].into()];
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal = vec![vec![44].into(), vec![].into()];
|
||||
header.seal[0] = rlp_encode(&44u64);
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_empty_step() {
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut config = test_aura_config();
|
||||
config.empty_steps_transition = 0;
|
||||
|
||||
// when empty step duplicates parent step
|
||||
let mut header = Header {
|
||||
seal: vec![
|
||||
vec![45].into(),
|
||||
vec![142].into(),
|
||||
SealedEmptyStep::rlp_of(&[sealed_empty_step(&validators, &genesis().compute_hash(), 42)]),
|
||||
],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().compute_hash(),
|
||||
..Default::default()
|
||||
};
|
||||
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 mut wrong_sealed_empty_step = sealed_empty_step(&validators, &genesis().compute_hash(), 43);
|
||||
wrong_sealed_empty_step.signature = Default::default();
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[wrong_sealed_empty_step]);
|
||||
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;
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
||||
sealed_empty_step(&validators, &genesis().compute_hash(), 44),
|
||||
sealed_empty_step(&validators, &genesis().compute_hash(), 43),
|
||||
]);
|
||||
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
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
||||
sealed_empty_step(&validators, &genesis().compute_hash(), 43),
|
||||
sealed_empty_step(&validators, &genesis().compute_hash(), 44),
|
||||
]);
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -647,33 +633,19 @@ mod tests {
|
||||
config.validate_score_transition = 0;
|
||||
|
||||
// when chain score is invalid
|
||||
let mut header = Header {
|
||||
seal: vec![vec![43].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().compute_hash(),
|
||||
..Default::default()
|
||||
};
|
||||
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
|
||||
header.difficulty = calculate_score(42, 43, 0);
|
||||
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 validators = vec![validator(0), validator(1), validator(2)];
|
||||
let good_header = signed_header(
|
||||
&validators,
|
||||
Header {
|
||||
author: validators[1].address().as_fixed_bytes().into(),
|
||||
seal: vec![vec![43].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().compute_hash(),
|
||||
..Default::default()
|
||||
},
|
||||
43,
|
||||
);
|
||||
let good_header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(1));
|
||||
|
||||
// when header author is invalid
|
||||
let mut header = good_header.clone();
|
||||
@@ -693,7 +665,7 @@ mod tests {
|
||||
fn pool_verifies_known_blocks() {
|
||||
// when header is known
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (block_i(3, validators), None)),
|
||||
default_accept_into_pool(|validators| (HeaderBuilder::with_parent_number(2).sign_by_set(validators), None)),
|
||||
Err(Error::KnownHeader),
|
||||
);
|
||||
}
|
||||
@@ -703,7 +675,9 @@ mod tests {
|
||||
// when header number is less than finalized
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
custom_block_i(2, validators, |header| header.gas_limit += 1.into()),
|
||||
HeaderBuilder::with_parent_number(1)
|
||||
.gas_limit((GAS_LIMIT + 1).into())
|
||||
.sign_by_set(validators),
|
||||
None,
|
||||
),),
|
||||
Err(Error::AncientHeader),
|
||||
@@ -731,7 +705,7 @@ mod tests {
|
||||
fn pool_rejects_headers_with_redundant_receipts() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
block_i(4, validators),
|
||||
HeaderBuilder::with_parent_number(3).sign_by_set(validators),
|
||||
Some(vec![Receipt {
|
||||
gas_used: 1.into(),
|
||||
log_bloom: (&[0xff; 256]).into(),
|
||||
@@ -747,7 +721,7 @@ mod tests {
|
||||
fn pool_verifies_future_block_number() {
|
||||
// when header is too far from the future
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (custom_block_i(4, validators, |header| header.number = 100), None,),),
|
||||
default_accept_into_pool(|validators| (HeaderBuilder::with_number(100).sign_by_set(&validators), None),),
|
||||
Err(Error::UnsignedTooFarInTheFuture),
|
||||
);
|
||||
}
|
||||
@@ -758,8 +732,9 @@ mod tests {
|
||||
// checks for DoubleVote
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| (
|
||||
custom_block_i(4, validators, |header| header.seal[0] =
|
||||
block_i(3, validators).seal[0].clone()),
|
||||
HeaderBuilder::with_parent_number(3)
|
||||
.step(GENESIS_STEP + 3)
|
||||
.sign_by_set(&validators),
|
||||
None,
|
||||
),),
|
||||
Err(Error::DoubleVote),
|
||||
@@ -772,21 +747,7 @@ mod tests {
|
||||
// (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(|validators| (
|
||||
signed_header(
|
||||
validators,
|
||||
Header {
|
||||
author: validators[1].address().as_fixed_bytes().into(),
|
||||
seal: vec![vec![8].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: [42; 32].into(),
|
||||
number: 8,
|
||||
..Default::default()
|
||||
},
|
||||
43
|
||||
),
|
||||
None,
|
||||
)),
|
||||
default_accept_into_pool(|_| (HeaderBuilder::with_number(8).step(8).sign_by(&validator(1)), None,)),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
}
|
||||
@@ -796,7 +757,7 @@ mod tests {
|
||||
let mut hash = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = block_i(4, &validators);
|
||||
let header = HeaderBuilder::with_parent_number(3).sign_by_set(validators);
|
||||
hash = Some(header.compute_hash());
|
||||
(header, None)
|
||||
}),
|
||||
@@ -814,32 +775,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pool_verifies_header_with_unknown_parent() {
|
||||
let mut hash = None;
|
||||
let mut id = None;
|
||||
let mut parent_id = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = signed_header(
|
||||
validators,
|
||||
Header {
|
||||
author: validators[2].address().as_fixed_bytes().into(),
|
||||
seal: vec![vec![47].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: [42; 32].into(),
|
||||
number: 5,
|
||||
..Default::default()
|
||||
},
|
||||
47,
|
||||
);
|
||||
hash = Some(header.compute_hash());
|
||||
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![(4u64, [42u8; 32]).encode(),],
|
||||
vec![parent_id.unwrap().encode()],
|
||||
// header provides two tags
|
||||
vec![
|
||||
(5u64, validators_addresses(3)[2]).encode(),
|
||||
(5u64, hash.unwrap()).encode(),
|
||||
],
|
||||
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||
)),
|
||||
);
|
||||
}
|
||||
@@ -852,55 +803,36 @@ mod tests {
|
||||
change_validators_set_at(3, validators_addresses(1), None);
|
||||
|
||||
// header is signed using wrong set
|
||||
let header = signed_header(
|
||||
actual_validators,
|
||||
Header {
|
||||
author: actual_validators[2].address().as_fixed_bytes().into(),
|
||||
seal: vec![vec![47].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: [42; 32].into(),
|
||||
number: 5,
|
||||
..Default::default()
|
||||
},
|
||||
47,
|
||||
);
|
||||
let header = HeaderBuilder::with_number(5)
|
||||
.step(GENESIS_STEP + 2)
|
||||
.sign_by_set(actual_validators);
|
||||
|
||||
(header, None)
|
||||
}),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
|
||||
let mut hash = None;
|
||||
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 = signed_header(
|
||||
actual_validators,
|
||||
Header {
|
||||
author: actual_validators[2].address().as_fixed_bytes().into(),
|
||||
seal: vec![vec![47].into(), vec![].into()],
|
||||
gas_limit: test_aura_config().min_gas_limit,
|
||||
parent_hash: [42; 32].into(),
|
||||
number: 5,
|
||||
..Default::default()
|
||||
},
|
||||
47,
|
||||
);
|
||||
hash = Some(header.compute_hash());
|
||||
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![(4u64, [42u8; 32]).encode(),],
|
||||
vec![parent_id.unwrap().encode(),],
|
||||
// header provides two tags
|
||||
vec![
|
||||
(5u64, validators_addresses(3)[2]).encode(),
|
||||
(5u64, hash.unwrap()).encode(),
|
||||
],
|
||||
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||
)),
|
||||
);
|
||||
}
|
||||
@@ -909,9 +841,9 @@ mod tests {
|
||||
fn pool_rejects_headers_with_invalid_receipts() {
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = custom_block_i(4, &validators, |header| {
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
});
|
||||
let header = HeaderBuilder::with_parent_number(3)
|
||||
.log_bloom((&[0xff; 256]).into())
|
||||
.sign_by_set(validators);
|
||||
(header, Some(vec![validators_change_recept(Default::default())]))
|
||||
}),
|
||||
Err(Error::TransactionsReceiptsMismatch),
|
||||
@@ -923,12 +855,14 @@ mod tests {
|
||||
let mut hash = None;
|
||||
assert_eq!(
|
||||
default_accept_into_pool(|validators| {
|
||||
let header = custom_block_i(4, &validators, |header| {
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
header.receipts_root = "81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45"
|
||||
.parse()
|
||||
.unwrap();
|
||||
});
|
||||
let header = HeaderBuilder::with_parent_number(3)
|
||||
.log_bloom((&[0xff; 256]).into())
|
||||
.receipts_root(
|
||||
"81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.sign_by_set(validators);
|
||||
hash = Some(header.compute_hash());
|
||||
(header, Some(vec![validators_change_recept(Default::default())]))
|
||||
}),
|
||||
|
||||
@@ -20,9 +20,6 @@ hash-db = { version = "0.15.2", default-features = false }
|
||||
triehash = { version = "0.8.2", default-features = false }
|
||||
plain_hasher = { version = "0.2.2", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2"
|
||||
|
||||
# Substrate Based Dependencies
|
||||
[dependencies.sp-api]
|
||||
version = "2.0.0-rc3"
|
||||
@@ -48,9 +45,18 @@ default-features = false
|
||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||
git = "https://github.com/paritytech/substrate.git"
|
||||
|
||||
[dependencies.libsecp256k1]
|
||||
optional = true
|
||||
version = "0.3.4"
|
||||
default-features = false
|
||||
features = ["hmac"]
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
test-helpers = []
|
||||
test-helpers = ["libsecp256k1"]
|
||||
std = [
|
||||
"serde/std",
|
||||
"serde-big-array",
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
pub use parity_bytes::Bytes;
|
||||
pub use primitive_types::{H160, H256, H512, U128, U256};
|
||||
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub use rlp::encode as rlp_encode;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
@@ -49,6 +47,9 @@ pub type RawTransaction = Vec<u8>;
|
||||
/// An ethereum address.
|
||||
pub type Address = H160;
|
||||
|
||||
#[cfg(any(feature = "test-helpers", test))]
|
||||
pub mod signatures;
|
||||
|
||||
/// Complete header id.
|
||||
#[derive(Encode, Decode, Default, RuntimeDebug, PartialEq, Clone, Copy)]
|
||||
pub struct HeaderId {
|
||||
@@ -59,8 +60,8 @@ pub struct HeaderId {
|
||||
}
|
||||
|
||||
/// An Aura header.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Default, Serialize, Deserialize))]
|
||||
#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Header {
|
||||
/// Parent block hash.
|
||||
pub parent_hash: H256,
|
||||
@@ -457,6 +458,11 @@ pub fn compute_merkle_root<T: AsRef<[u8]>>(items: impl Iterator<Item = T>) -> H2
|
||||
triehash::ordered_trie_root::<Keccak256Hasher, _>(items)
|
||||
}
|
||||
|
||||
/// Get validator that should author the block at given step.
|
||||
pub fn step_validator<T>(header_validators: &[T], header_step: u64) -> &T {
|
||||
&header_validators[(header_step % header_validators.len() as u64) as usize]
|
||||
}
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// API for headers submitters.
|
||||
pub trait EthereumHeadersApi {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 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/>.
|
||||
//
|
||||
|
||||
//! Helpers related to signatures.
|
||||
//!
|
||||
//! Used for testing and benchmarking.
|
||||
|
||||
use crate::{public_to_address, rlp_encode, step_validator, Address, Header, H256, H520};
|
||||
|
||||
use secp256k1::{Message, PublicKey, SecretKey};
|
||||
|
||||
/// Utilities for signing headers.
|
||||
pub trait SignHeader {
|
||||
/// Signs header by given author.
|
||||
fn sign_by(self, author: &SecretKey) -> Header;
|
||||
/// Signs header by given authors set.
|
||||
fn sign_by_set(self, authors: &[SecretKey]) -> Header;
|
||||
}
|
||||
|
||||
impl SignHeader for Header {
|
||||
fn sign_by(mut self, author: &SecretKey) -> Self {
|
||||
self.author = secret_to_address(author);
|
||||
|
||||
let message = self.seal_hash(false).unwrap();
|
||||
let signature = sign(author, message);
|
||||
self.seal[1] = rlp_encode(&signature);
|
||||
self
|
||||
}
|
||||
|
||||
fn sign_by_set(self, authors: &[SecretKey]) -> Self {
|
||||
let step = self.step().unwrap();
|
||||
let author = step_validator(authors, step);
|
||||
self.sign_by(author)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return author's signature over given message.
|
||||
pub fn sign(author: &SecretKey, message: H256) -> H520 {
|
||||
let (signature, recovery_id) = secp256k1::sign(&Message::parse(message.as_fixed_bytes()), author);
|
||||
let mut raw_signature = [0u8; 65];
|
||||
raw_signature[..64].copy_from_slice(&signature.serialize());
|
||||
raw_signature[64] = recovery_id.serialize();
|
||||
raw_signature.into()
|
||||
}
|
||||
|
||||
/// Returns address corresponding to given secret key.
|
||||
pub fn secret_to_address(secret: &SecretKey) -> Address {
|
||||
let public = PublicKey::from_secret_key(secret);
|
||||
let mut raw_public = [0u8; 64];
|
||||
raw_public.copy_from_slice(&public.serialize()[1..]);
|
||||
public_to_address(&raw_public)
|
||||
}
|
||||
Reference in New Issue
Block a user