mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 22:11:06 +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"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate.git"
|
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]
|
[build-dependencies]
|
||||||
vergen = "3.1.0"
|
vergen = "3.1.0"
|
||||||
|
|
||||||
@@ -123,3 +133,14 @@ package = "substrate-build-script-utils"
|
|||||||
version = "2.0.0-rc3"
|
version = "2.0.0-rc3"
|
||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate.git"
|
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
|
// 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/>.
|
// 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;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
@@ -25,3 +25,15 @@ pub struct Cli {
|
|||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
pub run: RunCmd,
|
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
|
// 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/>.
|
// 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 crate::service;
|
||||||
|
use bridge_node_runtime::Block;
|
||||||
use sc_cli::SubstrateCli;
|
use sc_cli::SubstrateCli;
|
||||||
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
|
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
|
||||||
|
|
||||||
@@ -81,7 +82,20 @@ pub fn run() -> sc_cli::Result<()> {
|
|||||||
let cli = Cli::from_args();
|
let cli = Cli::from_args();
|
||||||
|
|
||||||
match &cli.subcommand {
|
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)?;
|
let runner = cli.create_runner(subcommand)?;
|
||||||
runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0))
|
runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ native_executor_instance!(
|
|||||||
pub Executor,
|
pub Executor,
|
||||||
bridge_node_runtime::api::dispatch,
|
bridge_node_runtime::api::dispatch,
|
||||||
bridge_node_runtime::native_version,
|
bridge_node_runtime::native_version,
|
||||||
|
frame_benchmarking::benchmarking::HostFunctions,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Starts a `ServiceBuilder` for a full service.
|
/// Starts a `ServiceBuilder` for a full service.
|
||||||
|
|||||||
@@ -195,6 +195,13 @@ default-features = false
|
|||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate/"
|
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]
|
[build-dependencies.wasm-builder-runner]
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
package = "substrate-wasm-builder-runner"
|
package = "substrate-wasm-builder-runner"
|
||||||
@@ -209,6 +216,7 @@ std = [
|
|||||||
"pallet-bridge-eth-poa/std",
|
"pallet-bridge-eth-poa/std",
|
||||||
"pallet-bridge-currency-exchange/std",
|
"pallet-bridge-currency-exchange/std",
|
||||||
"codec/std",
|
"codec/std",
|
||||||
|
"frame-benchmarking/std",
|
||||||
"frame-executive/std",
|
"frame-executive/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
@@ -234,3 +242,11 @@ std = [
|
|||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
"pallet-transaction-payment/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() {
|
fn main() {
|
||||||
WasmBuilder::new()
|
WasmBuilder::new()
|
||||||
.with_current_project()
|
.with_current_project()
|
||||||
.with_wasm_builder_from_crates("1.0.9")
|
.with_wasm_builder_from_crates("1.0.11")
|
||||||
.export_heap_base()
|
.export_heap_base()
|
||||||
.import_memory()
|
.import_memory()
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -573,6 +573,27 @@ impl_runtime_apis! {
|
|||||||
None
|
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)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ default-features = false
|
|||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate/"
|
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]
|
[dev-dependencies.sp-core]
|
||||||
version = "2.0.0-rc3"
|
version = "2.0.0-rc3"
|
||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
@@ -48,11 +55,13 @@ git = "https://github.com/paritytech/substrate/"
|
|||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
"serde",
|
|
||||||
"codec/std",
|
"codec/std",
|
||||||
"sp-std/std",
|
"frame-benchmarking/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"sp-runtime/std",
|
"serde",
|
||||||
"sp-currency-exchange/std",
|
"sp-currency-exchange/std",
|
||||||
|
"sp-std/std",
|
||||||
|
"sp-runtime/std",
|
||||||
]
|
]
|
||||||
|
runtime-benchmarks = ["frame-benchmarking"]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", optional = true }
|
serde = { version = "1.0", optional = true }
|
||||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
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 }
|
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", default-features = false }
|
||||||
|
|
||||||
# Substrate Based Dependencies
|
# Substrate Based Dependencies
|
||||||
@@ -41,21 +42,40 @@ default-features = false
|
|||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate/"
|
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
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# TODO: Stop renaming this on import
|
# TODO: Stop renaming this on import
|
||||||
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", features = ["std", "test-helpers"] }
|
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]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
"serde",
|
|
||||||
"codec/std",
|
"codec/std",
|
||||||
"sp-std/std",
|
"frame-benchmarking/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
"sp-runtime/std",
|
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"sp-io/std",
|
|
||||||
"primitives/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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 crate::{BridgeStorage, FinalityCache, HeaderToImport};
|
||||||
use frame_support::StorageMap;
|
use frame_support::StorageMap;
|
||||||
|
|
||||||
|
const TOTAL_VALIDATORS: usize = 5;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_header_author() {
|
fn verifies_header_author() {
|
||||||
custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |_| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
finalize_blocks(
|
finalize_blocks(
|
||||||
&BridgeStorage::<TestRuntime>::new(),
|
&BridgeStorage::<TestRuntime>::new(),
|
||||||
@@ -306,21 +308,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finalize_blocks_works() {
|
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
|
// let's say we have 5 validators (we need 'votes' from 3 validators to achieve
|
||||||
// finality)
|
// finality)
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|
||||||
// when header#1 is inserted, nothing is finalized (1 vote)
|
// when header#1 is inserted, nothing is finalized (1 vote)
|
||||||
let header1 = Header {
|
let header1 = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(0));
|
||||||
author: validator(0).address().as_fixed_bytes().into(),
|
|
||||||
parent_hash: genesis().compute_hash(),
|
|
||||||
number: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let id1 = header1.compute_id();
|
let id1 = header1.compute_id();
|
||||||
let mut header_to_import = HeaderToImport {
|
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,
|
is_best: true,
|
||||||
id: id1,
|
id: id1,
|
||||||
header: header1,
|
header: header1,
|
||||||
@@ -332,8 +329,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
finalize_blocks(
|
finalize_blocks(
|
||||||
&storage,
|
&storage,
|
||||||
genesis().compute_id(),
|
ctx.genesis.compute_id(),
|
||||||
(Default::default(), &validators_addresses(5)),
|
(Default::default(), &ctx.addresses),
|
||||||
id1,
|
id1,
|
||||||
None,
|
None,
|
||||||
&header_to_import.header,
|
&header_to_import.header,
|
||||||
@@ -345,19 +342,14 @@ mod tests {
|
|||||||
storage.insert_header(header_to_import.clone());
|
storage.insert_header(header_to_import.clone());
|
||||||
|
|
||||||
// when header#2 is inserted, nothing is finalized (2 votes)
|
// when header#2 is inserted, nothing is finalized (2 votes)
|
||||||
header_to_import.header = Header {
|
header_to_import.header = HeaderBuilder::with_parent_hash(id1.hash).sign_by(&validator(1));
|
||||||
author: validator(1).address().as_fixed_bytes().into(),
|
|
||||||
parent_hash: id1.hash,
|
|
||||||
number: 2,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
header_to_import.id = header_to_import.header.compute_id();
|
header_to_import.id = header_to_import.header.compute_id();
|
||||||
let id2 = header_to_import.header.compute_id();
|
let id2 = header_to_import.header.compute_id();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
finalize_blocks(
|
finalize_blocks(
|
||||||
&storage,
|
&storage,
|
||||||
genesis().compute_id(),
|
ctx.genesis.compute_id(),
|
||||||
(Default::default(), &validators_addresses(5)),
|
(Default::default(), &ctx.addresses),
|
||||||
id2,
|
id2,
|
||||||
None,
|
None,
|
||||||
&header_to_import.header,
|
&header_to_import.header,
|
||||||
@@ -369,19 +361,14 @@ mod tests {
|
|||||||
storage.insert_header(header_to_import.clone());
|
storage.insert_header(header_to_import.clone());
|
||||||
|
|
||||||
// when header#3 is inserted, header#1 is finalized (3 votes)
|
// when header#3 is inserted, header#1 is finalized (3 votes)
|
||||||
header_to_import.header = Header {
|
header_to_import.header = HeaderBuilder::with_parent_hash(id2.hash).sign_by(&validator(2));
|
||||||
author: validator(2).address().as_fixed_bytes().into(),
|
|
||||||
parent_hash: id2.hash,
|
|
||||||
number: 3,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
header_to_import.id = header_to_import.header.compute_id();
|
header_to_import.id = header_to_import.header.compute_id();
|
||||||
let id3 = header_to_import.header.compute_id();
|
let id3 = header_to_import.header.compute_id();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
finalize_blocks(
|
finalize_blocks(
|
||||||
&storage,
|
&storage,
|
||||||
genesis().compute_id(),
|
ctx.genesis.compute_id(),
|
||||||
(Default::default(), &validators_addresses(5)),
|
(Default::default(), &ctx.addresses),
|
||||||
id3,
|
id3,
|
||||||
None,
|
None,
|
||||||
&header_to_import.header,
|
&header_to_import.header,
|
||||||
@@ -404,11 +391,7 @@ mod tests {
|
|||||||
// 2) add votes from header#4 and header#5
|
// 2) add votes from header#4 and header#5
|
||||||
let validators = validators_addresses(5);
|
let validators = validators_addresses(5);
|
||||||
let headers = (1..6)
|
let headers = (1..6)
|
||||||
.map(|number| Header {
|
.map(|number| HeaderBuilder::with_number(number).sign_by(&validator(number as usize - 1)))
|
||||||
number: number,
|
|
||||||
author: validators[number as usize - 1],
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let ancestry = headers
|
let ancestry = headers
|
||||||
.iter()
|
.iter()
|
||||||
@@ -451,8 +434,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prepare_votes_respects_finality_cache() {
|
fn prepare_votes_respects_finality_cache() {
|
||||||
let validators_addresses = validators_addresses(5);
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
|
||||||
// we need signatures of 3 validators to finalize block
|
// we need signatures of 3 validators to finalize block
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|
||||||
@@ -462,14 +444,9 @@ mod tests {
|
|||||||
let mut hashes = Vec::new();
|
let mut hashes = Vec::new();
|
||||||
let mut headers = Vec::new();
|
let mut headers = Vec::new();
|
||||||
let mut ancestry = 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 {
|
for i in 1..10 {
|
||||||
let header = Header {
|
let header = HeaderBuilder::with_parent_hash(parent_hash).sign_by(&validator((i - 1) / 3));
|
||||||
author: validator((i - 1) / 3).address().as_fixed_bytes().into(),
|
|
||||||
parent_hash,
|
|
||||||
number: i as _,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let id = header.compute_id();
|
let id = header.compute_id();
|
||||||
insert_header(&mut storage, header.clone());
|
insert_header(&mut storage, header.clone());
|
||||||
hashes.push(id.hash);
|
hashes.push(id.hash);
|
||||||
@@ -486,9 +463,9 @@ mod tests {
|
|||||||
// check that votes at #7 are computed correctly without cache
|
// check that votes at #7 are computed correctly without cache
|
||||||
let expected_votes_at_7 = FinalityVotes {
|
let expected_votes_at_7 = FinalityVotes {
|
||||||
votes: vec![
|
votes: vec![
|
||||||
(validators_addresses[0].clone(), 3),
|
(ctx.addresses[0].clone(), 3),
|
||||||
(validators_addresses[1].clone(), 3),
|
(ctx.addresses[1].clone(), 3),
|
||||||
(validators_addresses[2].clone(), 1),
|
(ctx.addresses[2].clone(), 1),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -499,11 +476,11 @@ mod tests {
|
|||||||
prepare_votes(
|
prepare_votes(
|
||||||
storage.cached_finality_votes(
|
storage.cached_finality_votes(
|
||||||
&headers.get(5).unwrap().compute_id(),
|
&headers.get(5).unwrap().compute_id(),
|
||||||
&genesis().compute_id(),
|
&ctx.genesis.compute_id(),
|
||||||
|_| false,
|
|_| false,
|
||||||
),
|
),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
&validators_addresses.iter().collect(),
|
&ctx.addresses.iter().collect(),
|
||||||
id7,
|
id7,
|
||||||
headers.get(6).unwrap(),
|
headers.get(6).unwrap(),
|
||||||
None,
|
None,
|
||||||
@@ -514,12 +491,9 @@ mod tests {
|
|||||||
|
|
||||||
// cached votes at #5
|
// cached votes at #5
|
||||||
let expected_votes_at_5 = FinalityVotes {
|
let expected_votes_at_5 = FinalityVotes {
|
||||||
votes: vec![
|
votes: vec![(ctx.addresses[0].clone(), 3), (ctx.addresses[1].clone(), 2)]
|
||||||
(validators_addresses[0].clone(), 3),
|
.into_iter()
|
||||||
(validators_addresses[1].clone(), 2),
|
.collect(),
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
ancestry: ancestry[..5].iter().cloned().collect(),
|
ancestry: ancestry[..5].iter().cloned().collect(),
|
||||||
};
|
};
|
||||||
FinalityCache::<TestRuntime>::insert(hashes[4], expected_votes_at_5);
|
FinalityCache::<TestRuntime>::insert(hashes[4], expected_votes_at_5);
|
||||||
@@ -530,11 +504,11 @@ mod tests {
|
|||||||
prepare_votes(
|
prepare_votes(
|
||||||
storage.cached_finality_votes(
|
storage.cached_finality_votes(
|
||||||
&headers.get(5).unwrap().compute_id(),
|
&headers.get(5).unwrap().compute_id(),
|
||||||
&genesis().compute_id(),
|
&ctx.genesis.compute_id(),
|
||||||
|_| false,
|
|_| false,
|
||||||
),
|
),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
&validators_addresses.iter().collect(),
|
&ctx.addresses.iter().collect(),
|
||||||
id7,
|
id7,
|
||||||
headers.get(6).unwrap(),
|
headers.get(6).unwrap(),
|
||||||
None,
|
None,
|
||||||
@@ -546,12 +520,9 @@ mod tests {
|
|||||||
// when we're inserting header#7 and last finalized header is 3:
|
// when we're inserting header#7 and last finalized header is 3:
|
||||||
// check that votes at #7 are computed correctly with cache
|
// check that votes at #7 are computed correctly with cache
|
||||||
let expected_votes_at_7 = FinalityVotes {
|
let expected_votes_at_7 = FinalityVotes {
|
||||||
votes: vec![
|
votes: vec![(ctx.addresses[1].clone(), 3), (ctx.addresses[2].clone(), 1)]
|
||||||
(validators_addresses[1].clone(), 3),
|
.into_iter()
|
||||||
(validators_addresses[2].clone(), 1),
|
.collect(),
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
ancestry: ancestry[3..7].iter().cloned().collect(),
|
ancestry: ancestry[3..7].iter().cloned().collect(),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -562,7 +533,7 @@ mod tests {
|
|||||||
|hash| *hash == hashes[2],
|
|hash| *hash == hashes[2],
|
||||||
),
|
),
|
||||||
headers[2].compute_id(),
|
headers[2].compute_id(),
|
||||||
&validators_addresses.iter().collect(),
|
&ctx.addresses.iter().collect(),
|
||||||
id7,
|
id7,
|
||||||
headers.get(6).unwrap(),
|
headers.get(6).unwrap(),
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -162,17 +162,19 @@ pub fn header_import_requires_receipts<S: Storage>(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mock::{
|
use crate::mock::{
|
||||||
block_i, custom_block_i, custom_test_ext, genesis, signed_header, test_aura_config, test_validators_config,
|
run_test, secret_to_address, test_aura_config, test_validators_config, validator, validators_addresses,
|
||||||
validator, validators, validators_addresses, KeepSomeHeadersBehindBest, TestRuntime, GENESIS_STEP,
|
HeaderBuilder, KeepSomeHeadersBehindBest, TestRuntime, GAS_LIMIT,
|
||||||
};
|
};
|
||||||
use crate::validators::ValidatorsSource;
|
use crate::validators::ValidatorsSource;
|
||||||
use crate::{BlocksToPrune, BridgeStorage, Headers, PruningRange};
|
use crate::{BlocksToPrune, BridgeStorage, Headers, PruningRange};
|
||||||
use frame_support::{StorageMap, StorageValue};
|
use frame_support::{StorageMap, StorageValue};
|
||||||
use parity_crypto::publickey::KeyPair;
|
use secp256k1::SecretKey;
|
||||||
|
|
||||||
|
const TOTAL_VALIDATORS: usize = 3;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_finalized_block_competitors() {
|
fn rejects_finalized_block_competitors() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |_| {
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
storage.finalize_and_prune_headers(
|
storage.finalize_and_prune_headers(
|
||||||
Some(HeaderId {
|
Some(HeaderId {
|
||||||
@@ -198,10 +200,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_known_header() {
|
fn rejects_known_header() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators = validators(3);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
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!(
|
assert_eq!(
|
||||||
import_header(
|
import_header(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
@@ -209,7 +210,7 @@ mod tests {
|
|||||||
&test_aura_config(),
|
&test_aura_config(),
|
||||||
&test_validators_config(),
|
&test_validators_config(),
|
||||||
None,
|
None,
|
||||||
block.clone(),
|
header.clone(),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
@@ -222,7 +223,7 @@ mod tests {
|
|||||||
&test_aura_config(),
|
&test_aura_config(),
|
||||||
&test_validators_config(),
|
&test_validators_config(),
|
||||||
None,
|
None,
|
||||||
block,
|
header,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
@@ -233,14 +234,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn import_header_works() {
|
fn import_header_works() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators_config = ValidatorsConfiguration::Multi(vec![
|
let validators_config = ValidatorsConfiguration::Multi(vec![
|
||||||
(0, ValidatorsSource::List(validators_addresses(3))),
|
(0, ValidatorsSource::List(ctx.addresses.clone())),
|
||||||
(1, ValidatorsSource::List(validators_addresses(2))),
|
(1, ValidatorsSource::List(validators_addresses(2))),
|
||||||
]);
|
]);
|
||||||
let validators = validators(3);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
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();
|
let hash = header.compute_hash();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
import_header(
|
import_header(
|
||||||
@@ -267,9 +267,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn headers_are_pruned_during_import() {
|
fn headers_are_pruned_during_import() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators_config =
|
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 validators = vec![validator(0), validator(1), validator(2)];
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
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
|
// => since we want to keep 10 finalized blocks, we aren't pruning anything
|
||||||
let mut latest_block_id = Default::default();
|
let mut latest_block_id = Default::default();
|
||||||
for i in 1..11 {
|
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(
|
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&mut KeepSomeHeadersBehindBest::default(),
|
&mut KeepSomeHeadersBehindBest::default(),
|
||||||
@@ -289,26 +291,24 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match i {
|
match i {
|
||||||
2..=10 => assert_eq!(
|
2..=10 => assert_eq!(finalized_blocks, vec![(parent_id, Some(100))], "At {}", i,),
|
||||||
finalized_blocks,
|
|
||||||
vec![(block_i(i - 1, &validators).compute_id(), Some(100))],
|
|
||||||
"At {}",
|
|
||||||
i,
|
|
||||||
),
|
|
||||||
_ => assert_eq!(finalized_blocks, vec![], "At {}", i),
|
_ => assert_eq!(finalized_blocks, vec![], "At {}", i),
|
||||||
}
|
}
|
||||||
latest_block_id = rolling_last_block_id;
|
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
|
// header 11 finalizes headers [10] AND schedules change
|
||||||
// => we prune header#0
|
// => we prune header#0
|
||||||
let header11 = custom_block_i(11, &validators, |header| {
|
let header11 = HeaderBuilder::with_parent_number(10)
|
||||||
header.log_bloom = (&[0xff; 256]).into();
|
.log_bloom((&[0xff; 256]).into())
|
||||||
header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951"
|
.receipts_root(
|
||||||
.parse()
|
"ead6c772ba0083bbff497ba0f4efe47c199a2655401096c21ab7450b6c466d97"
|
||||||
.unwrap();
|
.parse()
|
||||||
});
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.sign_by_set(&validators);
|
||||||
|
let parent_id = header11.parent_id().unwrap();
|
||||||
let (rolling_last_block_id, finalized_blocks) = import_header(
|
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&mut KeepSomeHeadersBehindBest::default(),
|
&mut KeepSomeHeadersBehindBest::default(),
|
||||||
@@ -321,29 +321,20 @@ mod tests {
|
|||||||
)]),
|
)]),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(finalized_blocks, vec![(parent_id, Some(100))],);
|
||||||
finalized_blocks,
|
assert!(storage.header(&ctx.genesis.compute_hash()).is_none());
|
||||||
vec![(block_i(10, &validators).compute_id(), Some(100))],
|
|
||||||
);
|
|
||||||
assert!(storage.header(&genesis().compute_hash()).is_none());
|
|
||||||
latest_block_id = rolling_last_block_id;
|
latest_block_id = rolling_last_block_id;
|
||||||
|
|
||||||
// and now let's say validators 1 && 2 went offline
|
// 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
|
// => 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
|
// 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))];
|
let mut expected_blocks = vec![(header11.compute_id(), Some(101))];
|
||||||
for i in 12..25 {
|
for i in 12..25 {
|
||||||
let header = Header {
|
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
|
||||||
number: i as _,
|
.difficulty(i.into())
|
||||||
parent_hash: latest_block_id.hash,
|
.step(step)
|
||||||
gas_limit: 0x2000.into(),
|
.sign_by_set(&validators);
|
||||||
author: validator(2).address(),
|
|
||||||
seal: vec![vec![step].into(), vec![].into()],
|
|
||||||
difficulty: i.into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let header = signed_header(&validators, header, step as _);
|
|
||||||
expected_blocks.push((header.compute_id(), Some(102)));
|
expected_blocks.push((header.compute_id(), Some(102)));
|
||||||
let (rolling_last_block_id, finalized_blocks) = import_header(
|
let (rolling_last_block_id, finalized_blocks) = import_header(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
@@ -370,16 +361,10 @@ mod tests {
|
|||||||
// now let's insert block signed by validator 1
|
// now let's insert block signed by validator 1
|
||||||
// => blocks 11..24 are finalized and blocks 11..14 are pruned
|
// => blocks 11..24 are finalized and blocks 11..14 are pruned
|
||||||
step -= 2;
|
step -= 2;
|
||||||
let header = Header {
|
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
|
||||||
number: 25,
|
.difficulty(25.into())
|
||||||
parent_hash: latest_block_id.hash,
|
.step(step)
|
||||||
gas_limit: 0x2000.into(),
|
.sign_by_set(&validators);
|
||||||
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 (_, finalized_blocks) = import_header(
|
let (_, finalized_blocks) = import_header(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&mut KeepSomeHeadersBehindBest::default(),
|
&mut KeepSomeHeadersBehindBest::default(),
|
||||||
@@ -403,18 +388,9 @@ mod tests {
|
|||||||
|
|
||||||
fn import_custom_block<S: Storage>(
|
fn import_custom_block<S: Storage>(
|
||||||
storage: &mut S,
|
storage: &mut S,
|
||||||
validators: &[KeyPair],
|
validators: &[SecretKey],
|
||||||
number: u64,
|
header: Header,
|
||||||
step: u64,
|
|
||||||
customize: impl FnOnce(&mut Header),
|
|
||||||
) -> Result<HeaderId, Error> {
|
) -> 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();
|
let id = header.compute_id();
|
||||||
import_header(
|
import_header(
|
||||||
storage,
|
storage,
|
||||||
@@ -422,7 +398,7 @@ mod tests {
|
|||||||
&test_aura_config(),
|
&test_aura_config(),
|
||||||
&ValidatorsConfiguration::Single(ValidatorsSource::Contract(
|
&ValidatorsConfiguration::Single(ValidatorsSource::Contract(
|
||||||
[0; 20].into(),
|
[0; 20].into(),
|
||||||
validators.iter().map(|kp| kp.address()).collect(),
|
validators.iter().map(secret_to_address).collect(),
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
header,
|
header,
|
||||||
@@ -433,37 +409,41 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn import_of_non_best_block_may_finalize_blocks() {
|
fn import_of_non_best_block_may_finalize_blocks() {
|
||||||
const TOTAL_VALIDATORS: u8 = 3;
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators_addresses = validators_addresses(TOTAL_VALIDATORS);
|
|
||||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
|
||||||
let validators = validators(TOTAL_VALIDATORS);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|
||||||
// insert headers (H1, validator1), (H2, validator1), (H3, validator1)
|
// insert headers (H1, validator1), (H2, validator1), (H3, validator1)
|
||||||
// making H3 the best header, without finalizing anything (we need 2 signatures)
|
// making H3 the best header, without finalizing anything (we need 2 signatures)
|
||||||
let mut expected_best_block = Default::default();
|
let mut expected_best_block = Default::default();
|
||||||
for i in 1..4 {
|
for i in 1..4 {
|
||||||
let step = GENESIS_STEP + i * TOTAL_VALIDATORS as u64;
|
let step = 1 + i * TOTAL_VALIDATORS as u64;
|
||||||
expected_best_block = import_custom_block(&mut storage, &validators, i, step, |header| {
|
expected_best_block = import_custom_block(
|
||||||
header.author = validators_addresses[0];
|
&mut storage,
|
||||||
header.seal[0][0] = step as u8;
|
&ctx.validators,
|
||||||
})
|
HeaderBuilder::with_parent_number(i - 1)
|
||||||
|
.step(step)
|
||||||
|
.sign_by_set(&ctx.validators),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let (best_block, best_difficulty) = storage.best_block();
|
let (best_block, best_difficulty) = storage.best_block();
|
||||||
assert_eq!(best_block, expected_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
|
// insert headers (H1', validator1), (H2', validator2), finalizing H2, even though H3
|
||||||
// has better difficulty than H2' (because there are more steps involved)
|
// has better difficulty than H2' (because there are more steps involved)
|
||||||
let mut expected_finalized_block = Default::default();
|
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 {
|
for i in 1..3 {
|
||||||
let step = GENESIS_STEP + i;
|
let step = i;
|
||||||
let id = import_custom_block(&mut storage, &validators, i, step, |header| {
|
let id = import_custom_block(
|
||||||
header.gas_limit += 1.into();
|
&mut storage,
|
||||||
header.parent_hash = parent_hash;
|
&ctx.validators,
|
||||||
})
|
HeaderBuilder::with_parent_hash(parent_hash)
|
||||||
|
.step(step)
|
||||||
|
.gas_limit((GAS_LIMIT + 1).into())
|
||||||
|
.sign_by_set(&ctx.validators),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
parent_hash = id.hash;
|
parent_hash = id.hash;
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
@@ -479,83 +459,117 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn append_to_unfinalized_fork_fails() {
|
fn append_to_unfinalized_fork_fails() {
|
||||||
const TOTAL_VALIDATORS: u64 = 5;
|
const VALIDATORS: u64 = 5;
|
||||||
let validators_addresses = validators_addresses(TOTAL_VALIDATORS as _);
|
run_test(VALIDATORS as usize, |ctx| {
|
||||||
custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || {
|
|
||||||
let validators = validators(TOTAL_VALIDATORS as _);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|
||||||
// header1, authored by validator[2] is best common block between two competing forks
|
// 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.best_block().0, header1);
|
||||||
assert_eq!(storage.finalized_block().number, 0);
|
assert_eq!(storage.finalized_block().number, 0);
|
||||||
|
|
||||||
// validator[3] has authored header2 (nothing is finalized yet)
|
// 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.best_block().0, header2);
|
||||||
assert_eq!(storage.finalized_block().number, 0);
|
assert_eq!(storage.finalized_block().number, 0);
|
||||||
|
|
||||||
// validator[4] has authored header3 (header1 is finalized)
|
// 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.best_block().0, header3);
|
||||||
assert_eq!(storage.finalized_block(), header1);
|
assert_eq!(storage.finalized_block(), header1);
|
||||||
|
|
||||||
// validator[4] has authored 4 blocks: header2'...header5' (header1 is still finalized)
|
// 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| {
|
let header2_1 = import_custom_block(
|
||||||
header.gas_limit += 1.into();
|
&mut storage,
|
||||||
})
|
&ctx.validators,
|
||||||
|
HeaderBuilder::with_parent_number(1)
|
||||||
|
.gas_limit((GAS_LIMIT + 1).into())
|
||||||
|
.step(4)
|
||||||
|
.sign_by_set(&ctx.validators),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let header3_1 = import_custom_block(
|
let header3_1 = import_custom_block(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&validators,
|
&ctx.validators,
|
||||||
3,
|
HeaderBuilder::with_parent_hash(header2_1.hash)
|
||||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS,
|
.step(4 + VALIDATORS)
|
||||||
|header| {
|
.sign_by_set(&ctx.validators),
|
||||||
header.parent_hash = header2_1.hash;
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let header4_1 = import_custom_block(
|
let header4_1 = import_custom_block(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&validators,
|
&ctx.validators,
|
||||||
4,
|
HeaderBuilder::with_parent_hash(header3_1.hash)
|
||||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 2,
|
.step(4 + VALIDATORS * 2)
|
||||||
|header| {
|
.sign_by_set(&ctx.validators),
|
||||||
header.parent_hash = header3_1.hash;
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let header5_1 = import_custom_block(
|
let header5_1 = import_custom_block(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&validators,
|
&ctx.validators,
|
||||||
5,
|
HeaderBuilder::with_parent_hash(header4_1.hash)
|
||||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 3,
|
.step(4 + VALIDATORS * 3)
|
||||||
|header| {
|
.sign_by_set(&ctx.validators),
|
||||||
header.parent_hash = header4_1.hash;
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(storage.best_block().0, header5_1);
|
assert_eq!(storage.best_block().0, header5_1);
|
||||||
assert_eq!(storage.finalized_block(), header1);
|
assert_eq!(storage.finalized_block(), header1);
|
||||||
|
|
||||||
// when we import header4 { parent = header3 }, authored by validator[0], header2 is finalized
|
// 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.best_block().0, header5_1);
|
||||||
assert_eq!(storage.finalized_block(), header2);
|
assert_eq!(storage.finalized_block(), header2);
|
||||||
|
|
||||||
// when we import header5 { parent = header4 }, authored by validator[1], header3 is finalized
|
// 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| {
|
let header5 = import_custom_block(
|
||||||
header.parent_hash = header4.hash;
|
&mut storage,
|
||||||
})
|
&ctx.validators,
|
||||||
|
HeaderBuilder::with_parent_hash(header4.hash)
|
||||||
|
.step(6)
|
||||||
|
.sign_by_set(&ctx.validators),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(storage.best_block().0, header5_1);
|
assert_eq!(storage.best_block().0, header5);
|
||||||
assert_eq!(storage.finalized_block(), header3);
|
assert_eq!(storage.finalized_block(), header3);
|
||||||
|
|
||||||
// import of header2'' { parent = header1 } fails, because it has number < best_finalized
|
// import of header2'' { parent = header1 } fails, because it has number < best_finalized
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
import_custom_block(&mut storage, &validators, 2, GENESIS_STEP + 3, |header| {
|
import_custom_block(
|
||||||
header.gas_limit += 2.into();
|
&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),
|
Err(Error::AncientHeader),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -564,10 +578,11 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
import_custom_block(
|
import_custom_block(
|
||||||
&mut storage,
|
&mut storage,
|
||||||
&validators,
|
&ctx.validators,
|
||||||
6,
|
HeaderBuilder::with_parent_number(5)
|
||||||
GENESIS_STEP + 3 + TOTAL_VALIDATORS * 4,
|
.gas_limit((GAS_LIMIT + 1).into())
|
||||||
|_| ()
|
.step(5 + VALIDATORS * 4)
|
||||||
|
.sign_by_set(&ctx.validators),
|
||||||
),
|
),
|
||||||
Err(Error::TryingToFinalizeSibling),
|
Err(Error::TryingToFinalizeSibling),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,9 +37,15 @@ mod import;
|
|||||||
mod validators;
|
mod validators;
|
||||||
mod verification;
|
mod verification;
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
mod benchmarking;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mock;
|
mod mock;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||||
|
mod test_utils;
|
||||||
|
|
||||||
/// Maximal number of blocks we're pruning in single import call.
|
/// Maximal number of blocks we're pruning in single import call.
|
||||||
const MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT: u64 = 8;
|
const MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT: u64 = 8;
|
||||||
|
|
||||||
@@ -464,32 +470,11 @@ decl_storage! {
|
|||||||
"Initial validators set can't be empty",
|
"Initial validators set can't be empty",
|
||||||
);
|
);
|
||||||
|
|
||||||
let initial_hash = config.initial_header.compute_hash();
|
initialize_storage::<T>(
|
||||||
let initial_id = HeaderId {
|
&config.initial_header,
|
||||||
number: config.initial_header.number,
|
config.initial_difficulty,
|
||||||
hash: initial_hash,
|
&config.initial_validators,
|
||||||
};
|
);
|
||||||
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);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,6 +617,13 @@ impl<T: Trait> BridgeStorage<T> {
|
|||||||
// physically remove headers and (probably) obsolete validators sets
|
// physically remove headers and (probably) obsolete validators sets
|
||||||
while let Some(hash) = blocks_at_number.pop() {
|
while let Some(hash) = blocks_at_number.pop() {
|
||||||
let header = Headers::<T>::take(&hash);
|
let header = Headers::<T>::take(&hash);
|
||||||
|
frame_support::debug::trace!(
|
||||||
|
target: "runtime",
|
||||||
|
"Pruning PoA header: ({}, {})",
|
||||||
|
number,
|
||||||
|
hash,
|
||||||
|
);
|
||||||
|
|
||||||
ScheduledChanges::remove(hash);
|
ScheduledChanges::remove(hash);
|
||||||
FinalityCache::<T>::remove(hash);
|
FinalityCache::<T>::remove(hash);
|
||||||
if let Some(header) = header {
|
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.
|
/// Verify that transaction is included into given finalized block.
|
||||||
pub fn verify_transaction_finalized<S: Storage>(
|
pub fn verify_transaction_finalized<S: Storage>(
|
||||||
storage: &S,
|
storage: &S,
|
||||||
@@ -894,10 +933,13 @@ pub(crate) mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::finality::FinalityAncestor;
|
use crate::finality::FinalityAncestor;
|
||||||
use crate::mock::{
|
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;
|
use primitives::compute_merkle_root;
|
||||||
|
|
||||||
|
const TOTAL_VALIDATORS: usize = 3;
|
||||||
|
|
||||||
fn example_tx() -> Vec<u8> {
|
fn example_tx() -> Vec<u8> {
|
||||||
vec![42]
|
vec![42]
|
||||||
}
|
}
|
||||||
@@ -919,14 +961,13 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn with_headers_to_prune<T>(f: impl Fn(BridgeStorage<TestRuntime>) -> T) -> T {
|
fn with_headers_to_prune<T>(f: impl Fn(BridgeStorage<TestRuntime>) -> T) -> T {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators = validators(3);
|
|
||||||
for i in 1..10 {
|
for i in 1..10 {
|
||||||
let mut headers_by_number = Vec::with_capacity(5);
|
let mut headers_by_number = Vec::with_capacity(5);
|
||||||
for j in 0..5 {
|
for j in 0..5 {
|
||||||
let header = custom_block_i(i, &validators, |header| {
|
let header = HeaderBuilder::with_parent_number(i - 1)
|
||||||
header.gas_limit = header.gas_limit + U256::from(j);
|
.gas_limit((GAS_LIMIT + j).into())
|
||||||
});
|
.sign_by_set(&ctx.validators);
|
||||||
let hash = header.compute_hash();
|
let hash = header.compute_hash();
|
||||||
headers_by_number.push(hash);
|
headers_by_number.push(hash);
|
||||||
Headers::<TestRuntime>::insert(
|
Headers::<TestRuntime>::insert(
|
||||||
@@ -1082,21 +1123,20 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finality_votes_are_cached() {
|
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 mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
let interval = <TestRuntime as Trait>::FinalityVotesCachingInterval::get().unwrap();
|
let interval = <TestRuntime as Trait>::FinalityVotesCachingInterval::get().unwrap();
|
||||||
|
|
||||||
// for all headers with number < interval, cache entry is not created
|
// for all headers with number < interval, cache entry is not created
|
||||||
let validators = validators(3);
|
|
||||||
for i in 1..interval {
|
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();
|
let id = header.compute_id();
|
||||||
insert_header(&mut storage, header);
|
insert_header(&mut storage, header);
|
||||||
assert_eq!(FinalityCache::<TestRuntime>::get(&id.hash), None);
|
assert_eq!(FinalityCache::<TestRuntime>::get(&id.hash), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for header with number = interval, cache entry is created
|
// 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();
|
let header_with_entry_hash = header_with_entry.compute_hash();
|
||||||
insert_header(&mut storage, header_with_entry);
|
insert_header(&mut storage, header_with_entry);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1116,13 +1156,12 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cached_finality_votes_finds_entry() {
|
fn cached_finality_votes_finds_entry() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
// insert 5 headers
|
// insert 5 headers
|
||||||
let validators = validators(3);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
let mut headers = Vec::new();
|
let mut headers = Vec::new();
|
||||||
for i in 1..5 {
|
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());
|
headers.push(header.clone());
|
||||||
insert_header(&mut storage, header);
|
insert_header(&mut storage, header);
|
||||||
}
|
}
|
||||||
@@ -1177,19 +1216,18 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cached_finality_votes_stops_at_finalized_sibling() {
|
fn cached_finality_votes_stops_at_finalized_sibling() {
|
||||||
custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| {
|
run_test(TOTAL_VALIDATORS, |ctx| {
|
||||||
let validators = validators(3);
|
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|
||||||
// insert header1
|
// 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();
|
let header1_id = header1.compute_id();
|
||||||
insert_header(&mut storage, header1);
|
insert_header(&mut storage, header1);
|
||||||
|
|
||||||
// insert header1' - sibling of header1
|
// insert header1' - sibling of header1
|
||||||
let header1s = custom_block_i(1, &validators, |header| {
|
let header1s = HeaderBuilder::with_parent_number(0)
|
||||||
header.gas_limit += 1.into();
|
.gas_limit((GAS_LIMIT + 1).into())
|
||||||
});
|
.sign_by_set(&ctx.validators);
|
||||||
let header1s_id = header1s.compute_id();
|
let header1s_id = header1s.compute_id();
|
||||||
insert_header(&mut storage, header1s);
|
insert_header(&mut storage, header1s);
|
||||||
|
|
||||||
@@ -1214,7 +1252,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_works_for_best_finalized_header() {
|
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();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &vec![example_tx()],),
|
verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &vec![example_tx()],),
|
||||||
@@ -1225,7 +1263,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_works_for_best_finalized_header_ancestor() {
|
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();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
insert_header(&mut storage, example_header_parent());
|
insert_header(&mut storage, example_header_parent());
|
||||||
insert_header(&mut storage, example_header());
|
insert_header(&mut storage, example_header());
|
||||||
@@ -1239,7 +1277,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_proof_with_missing_tx() {
|
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();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
||||||
@@ -1250,7 +1288,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_unknown_header() {
|
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();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
verify_transaction_finalized(&storage, example_header().compute_hash(), 1, &vec![],),
|
||||||
@@ -1261,7 +1299,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_unfinalized_header() {
|
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();
|
let mut storage = BridgeStorage::<TestRuntime>::new();
|
||||||
insert_header(&mut storage, example_header_parent());
|
insert_header(&mut storage, example_header_parent());
|
||||||
insert_header(&mut storage, example_header());
|
insert_header(&mut storage, example_header());
|
||||||
@@ -1274,7 +1312,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_finalized_header_sibling() {
|
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();
|
let mut finalized_header_sibling = example_header();
|
||||||
finalized_header_sibling.timestamp = 1;
|
finalized_header_sibling.timestamp = 1;
|
||||||
let finalized_header_sibling_hash = finalized_header_sibling.compute_hash();
|
let finalized_header_sibling_hash = finalized_header_sibling.compute_hash();
|
||||||
@@ -1293,7 +1331,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_finalized_header_uncle() {
|
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();
|
let mut finalized_header_uncle = example_header_parent();
|
||||||
finalized_header_uncle.timestamp = 1;
|
finalized_header_uncle.timestamp = 1;
|
||||||
let finalized_header_uncle_hash = finalized_header_uncle.compute_hash();
|
let finalized_header_uncle_hash = finalized_header_uncle.compute_hash();
|
||||||
@@ -1312,7 +1350,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_transaction_finalized_rejects_invalid_proof() {
|
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();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
verify_transaction_finalized(
|
verify_transaction_finalized(
|
||||||
|
|||||||
@@ -14,14 +14,15 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// 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::finality::FinalityVotes;
|
||||||
use crate::validators::{ValidatorsConfiguration, ValidatorsSource};
|
use crate::validators::{ValidatorsConfiguration, ValidatorsSource};
|
||||||
use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, HeadersByNumber, PruningStrategy, Storage, Trait};
|
use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, PruningStrategy, Storage, Trait};
|
||||||
use frame_support::StorageMap;
|
|
||||||
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
|
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 primitives::{Address, Header, H256, U256};
|
||||||
|
use secp256k1::SecretKey;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
testing::Header as SubstrateHeader,
|
testing::Header as SubstrateHeader,
|
||||||
traits::{BlakeTwo256, IdentityLookup},
|
traits::{BlakeTwo256, IdentityLookup},
|
||||||
@@ -84,8 +85,17 @@ impl Trait for TestRuntime {
|
|||||||
type OnHeadersSubmitted = ();
|
type OnHeadersSubmitted = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step of genesis header.
|
/// Test context.
|
||||||
pub const GENESIS_STEP: u64 = 42;
|
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.
|
/// Aura configuration that is used in tests by default.
|
||||||
pub fn test_aura_config() -> AuraConfiguration {
|
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.
|
/// Genesis header that is used in tests by default.
|
||||||
pub fn genesis() -> Header {
|
pub fn genesis() -> Header {
|
||||||
Header {
|
HeaderBuilder::genesis().sign_by(&validator(0))
|
||||||
seal: vec![vec![GENESIS_STEP as _].into(), vec![].into()],
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build default i-th block, using data from runtime storage.
|
/// Run test with default genesis header.
|
||||||
pub fn block_i(number: u64, validators: &[KeyPair]) -> Header {
|
pub fn run_test<T>(total_validators: usize, test: impl FnOnce(TestContext) -> T) -> T {
|
||||||
custom_block_i(number, validators, |_| {})
|
run_test_with_genesis(genesis(), total_validators, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build custom i-th block, using data from runtime storage.
|
/// Run test with default genesis header.
|
||||||
pub fn custom_block_i(number: u64, validators: &[KeyPair], customize: impl FnOnce(&mut Header)) -> Header {
|
pub fn run_test_with_genesis<T>(genesis: Header, total_validators: usize, test: impl FnOnce(TestContext) -> T) -> T {
|
||||||
let validator_index: u8 = (number % (validators.len() as u64)) as _;
|
let validators = validators(total_validators);
|
||||||
let mut header = Header {
|
let addresses = validators_addresses(total_validators);
|
||||||
number,
|
sp_io::TestExternalities::new(
|
||||||
parent_hash: HeadersByNumber::get(number - 1).unwrap()[0].clone(),
|
GenesisConfig {
|
||||||
gas_limit: 0x2000.into(),
|
initial_header: genesis.clone(),
|
||||||
author: validator(validator_index).address(),
|
initial_difficulty: 0.into(),
|
||||||
seal: vec![vec![(number + GENESIS_STEP) as u8].into(), vec![].into()],
|
initial_validators: addresses.clone(),
|
||||||
difficulty: number.into(),
|
}
|
||||||
..Default::default()
|
.build_storage::<TestRuntime>()
|
||||||
};
|
.unwrap(),
|
||||||
customize(&mut header);
|
)
|
||||||
signed_header(validators, header, number + GENESIS_STEP)
|
.execute_with(|| {
|
||||||
|
test(TestContext {
|
||||||
|
genesis,
|
||||||
|
total_validators,
|
||||||
|
validators,
|
||||||
|
addresses,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build signed header from given header.
|
/// Insert unverified header into storage.
|
||||||
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.
|
|
||||||
pub fn insert_header<S: Storage>(storage: &mut S, header: Header) {
|
pub fn insert_header<S: Storage>(storage: &mut S, header: Header) {
|
||||||
storage.insert_header(HeaderToImport {
|
storage.insert_header(HeaderToImport {
|
||||||
context: storage.import_context(None, &header.parent_hash).unwrap(),
|
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)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
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 crate::{BridgeStorage, Headers, ScheduledChange, ScheduledChanges, StoredHeader};
|
||||||
use frame_support::StorageMap;
|
use frame_support::StorageMap;
|
||||||
use primitives::{TransactionOutcome, H256};
|
use primitives::{TransactionOutcome, H256};
|
||||||
|
|
||||||
|
const TOTAL_VALIDATORS: usize = 3;
|
||||||
|
|
||||||
pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt {
|
pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt {
|
||||||
Receipt {
|
Receipt {
|
||||||
gas_used: 0.into(),
|
gas_used: 0.into(),
|
||||||
@@ -425,7 +422,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_finalize_with_scheduled_change(scheduled_at: Option<HeaderId>) -> Option<ChangeToEnact> {
|
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 config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||||
let validators = Validators::new(&config);
|
let validators = Validators::new(&config);
|
||||||
let storage = BridgeStorage::<TestRuntime>::new();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
|
|||||||
@@ -15,10 +15,12 @@
|
|||||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::validators::{step_validator, Validators, ValidatorsConfiguration};
|
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||||
use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage};
|
use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage};
|
||||||
use codec::Encode;
|
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_io::crypto::secp256k1_ecdsa_recover;
|
||||||
use sp_std::{vec, vec::Vec};
|
use sp_std::{vec, vec::Vec};
|
||||||
|
|
||||||
@@ -157,9 +159,16 @@ pub fn verify_aura_header<S: Storage>(
|
|||||||
contextless_checks(config, header)?;
|
contextless_checks(config, header)?;
|
||||||
|
|
||||||
// the rest of checks requires access to the parent header
|
// the rest of checks requires access to the parent header
|
||||||
let context = storage
|
let context = storage.import_context(submitter, &header.parent_hash).ok_or_else(|| {
|
||||||
.import_context(submitter, &header.parent_hash)
|
frame_support::debug::warn!(
|
||||||
.ok_or(Error::MissingParentBlock)?;
|
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)?;
|
let header_step = contextual_checks(config, &context, None, header)?;
|
||||||
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
||||||
|
|
||||||
@@ -263,7 +272,7 @@ fn validator_checks(
|
|||||||
header: &Header,
|
header: &Header,
|
||||||
header_step: u64,
|
header_step: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let expected_validator = step_validator(validators, header_step);
|
let expected_validator = *step_validator(validators, header_step);
|
||||||
if header.author != expected_validator {
|
if header.author != expected_validator {
|
||||||
return Err(Error::NotValidator);
|
return Err(Error::NotValidator);
|
||||||
}
|
}
|
||||||
@@ -282,7 +291,7 @@ fn validator_checks(
|
|||||||
|
|
||||||
/// Returns expected number of seal fields in the header.
|
/// Returns expected number of seal fields in the header.
|
||||||
fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> usize {
|
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
|
3
|
||||||
} else {
|
} else {
|
||||||
2
|
2
|
||||||
@@ -291,13 +300,13 @@ fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> u
|
|||||||
|
|
||||||
/// Verify single sealed empty step.
|
/// Verify single sealed empty step.
|
||||||
fn verify_empty_step(parent_hash: &H256, step: &SealedEmptyStep, validators: &[Address]) -> bool {
|
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);
|
let message = step.message(parent_hash);
|
||||||
verify_signature(&expected_validator, &step.signature, &message)
|
verify_signature(&expected_validator, &step.signature, &message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
/// 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)
|
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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mock::{
|
use crate::mock::{
|
||||||
block_i, custom_block_i, custom_test_ext, genesis, insert_header, signed_header, test_aura_config, validator,
|
insert_header, run_test_with_genesis, test_aura_config, validator, validator_address, validators_addresses,
|
||||||
validators_addresses, AccountId, TestRuntime,
|
AccountId, HeaderBuilder, TestRuntime, GAS_LIMIT,
|
||||||
};
|
};
|
||||||
use crate::validators::{tests::validators_change_recept, ValidatorsSource};
|
use crate::validators::{tests::validators_change_recept, ValidatorsSource};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -353,25 +362,18 @@ mod tests {
|
|||||||
ScheduledChanges, ValidatorsSet, ValidatorsSets,
|
ScheduledChanges, ValidatorsSet, ValidatorsSets,
|
||||||
};
|
};
|
||||||
use frame_support::{StorageMap, StorageValue};
|
use frame_support::{StorageMap, StorageValue};
|
||||||
use parity_crypto::publickey::{sign, KeyPair};
|
|
||||||
use primitives::{rlp_encode, TransactionOutcome, H520};
|
use primitives::{rlp_encode, TransactionOutcome, H520};
|
||||||
|
use secp256k1::SecretKey;
|
||||||
|
|
||||||
fn sealed_empty_step(validators: &[KeyPair], parent_hash: &H256, step: u64) -> SealedEmptyStep {
|
const GENESIS_STEP: u64 = 42;
|
||||||
let mut empty_step = SealedEmptyStep {
|
const TOTAL_VALIDATORS: usize = 3;
|
||||||
step,
|
|
||||||
signature: Default::default(),
|
fn genesis() -> Header {
|
||||||
};
|
HeaderBuilder::genesis().step(GENESIS_STEP).sign_by(&validator(0))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result<ImportContext<AccountId>, Error> {
|
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();
|
let storage = BridgeStorage::<TestRuntime>::new();
|
||||||
verify_aura_header(&storage, &config, None, header)
|
verify_aura_header(&storage, &config, None, header)
|
||||||
})
|
})
|
||||||
@@ -382,17 +384,17 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_accept_into_pool(
|
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> {
|
) -> 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 validators = vec![validator(0), validator(1), validator(2)];
|
||||||
let mut storage = BridgeStorage::<TestRuntime>::new();
|
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);
|
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();
|
let block2_id = block2.compute_id();
|
||||||
insert_header(&mut storage, block2);
|
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);
|
insert_header(&mut storage, block3);
|
||||||
|
|
||||||
FinalizedBlock::put(block2_id);
|
FinalizedBlock::put(block2_id);
|
||||||
@@ -468,32 +470,26 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn verifies_header_number() {
|
fn verifies_header_number() {
|
||||||
// when number is u64::max_value()
|
// when number is u64::max_value()
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(u64::max_value()).sign_by(&validator(0));
|
||||||
seal: vec![vec![].into(), vec![].into(), vec![].into()],
|
|
||||||
number: u64::max_value(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
|
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||||
|
|
||||||
// when header is < u64::max_value()
|
// when header is < u64::max_value()
|
||||||
header.seal = vec![vec![].into(), vec![].into()];
|
let header = HeaderBuilder::with_number(u64::max_value() - 1).sign_by(&validator(0));
|
||||||
header.number -= 1;
|
|
||||||
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
|
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_gas_used() {
|
fn verifies_gas_used() {
|
||||||
// when gas used is larger than gas limit
|
// when gas used is larger than gas limit
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(1)
|
||||||
seal: vec![vec![].into(), vec![].into()],
|
.gas_used((GAS_LIMIT + 1).into())
|
||||||
gas_used: 1.into(),
|
.sign_by(&validator(0));
|
||||||
gas_limit: 0.into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||||
|
|
||||||
// when gas used is less than gas limit
|
// 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));
|
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,67 +500,62 @@ mod tests {
|
|||||||
config.max_gas_limit = 200.into();
|
config.max_gas_limit = 200.into();
|
||||||
|
|
||||||
// when limit is lower than expected
|
// when limit is lower than expected
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(1)
|
||||||
seal: vec![vec![].into(), vec![].into()],
|
.gas_limit(50.into())
|
||||||
gas_limit: 50.into(),
|
.sign_by(&validator(0));
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||||
|
|
||||||
// when limit is larger than expected
|
// 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));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||||
|
|
||||||
// when limit is within expected range
|
// 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));
|
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_extra_data_len() {
|
fn verifies_extra_data_len() {
|
||||||
// when extra data is too large
|
// when extra data is too large
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(1)
|
||||||
seal: vec![vec![].into(), vec![].into()],
|
.extra_data(std::iter::repeat(42).take(1000).collect::<Vec<_>>())
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
.sign_by(&validator(0));
|
||||||
extra_data: std::iter::repeat(42).take(1000).collect::<Vec<_>>().into(),
|
|
||||||
number: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||||
|
|
||||||
// when extra data size is OK
|
// 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));
|
assert_ne!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_timestamp() {
|
fn verifies_timestamp() {
|
||||||
// when timestamp overflows i32
|
// when timestamp overflows i32
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(1)
|
||||||
seal: vec![vec![].into(), vec![].into()],
|
.timestamp(i32::max_value() as u64 + 1)
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
.sign_by(&validator(0));
|
||||||
timestamp: i32::max_value() as u64 + 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
|
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||||
|
|
||||||
// when timestamp doesn't overflow i32
|
// 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));
|
assert_ne!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_parent_existence() {
|
fn verifies_parent_existence() {
|
||||||
// when there's no parent in the storage
|
// when there's no parent in the storage
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_number(1).sign_by(&validator(0));
|
||||||
seal: vec![vec![].into(), vec![].into()],
|
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
|
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||||
|
|
||||||
// when parent is in the storage
|
// 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));
|
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,11 +571,11 @@ mod tests {
|
|||||||
assert_eq!(default_verify(&header), Err(Error::MissingStep));
|
assert_eq!(default_verify(&header), Err(Error::MissingStep));
|
||||||
|
|
||||||
// when step is the same as for the parent block
|
// 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));
|
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
|
||||||
|
|
||||||
// when step is OK
|
// 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));
|
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
|
||||||
|
|
||||||
// now check with validate_step check enabled
|
// now check with validate_step check enabled
|
||||||
@@ -592,52 +583,47 @@ mod tests {
|
|||||||
config.validate_step_transition = 0;
|
config.validate_step_transition = 0;
|
||||||
|
|
||||||
// when step is lesser that for the parent block
|
// when step is lesser that for the parent block
|
||||||
|
header.seal[0] = rlp_encode(&40u64);
|
||||||
header.seal = vec![vec![40].into(), vec![].into()];
|
header.seal = vec![vec![40].into(), vec![].into()];
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||||
|
|
||||||
// when step is OK
|
// 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));
|
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_empty_step() {
|
fn verifies_empty_step() {
|
||||||
let validators = vec![validator(0), validator(1), validator(2)];
|
|
||||||
let mut config = test_aura_config();
|
let mut config = test_aura_config();
|
||||||
config.empty_steps_transition = 0;
|
config.empty_steps_transition = 0;
|
||||||
|
|
||||||
// when empty step duplicates parent step
|
// when empty step duplicates parent step
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_parent(&genesis())
|
||||||
seal: vec![
|
.empty_steps(&[(&validator(0), GENESIS_STEP)])
|
||||||
vec![45].into(),
|
.step(GENESIS_STEP + 3)
|
||||||
vec![142].into(),
|
.sign_by(&validator(3));
|
||||||
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()
|
|
||||||
};
|
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||||
|
|
||||||
// when empty step signature check fails
|
// when empty step signature check fails
|
||||||
let mut wrong_sealed_empty_step = sealed_empty_step(&validators, &genesis().compute_hash(), 43);
|
let header = HeaderBuilder::with_parent(&genesis())
|
||||||
wrong_sealed_empty_step.signature = Default::default();
|
.empty_steps(&[(&validator(100), GENESIS_STEP + 1)])
|
||||||
header.seal[2] = SealedEmptyStep::rlp_of(&[wrong_sealed_empty_step]);
|
.step(GENESIS_STEP + 3)
|
||||||
|
.sign_by(&validator(3));
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||||
|
|
||||||
// when we are accepting strict empty steps and they come not in order
|
// when we are accepting strict empty steps and they come not in order
|
||||||
config.strict_empty_steps_transition = 0;
|
config.strict_empty_steps_transition = 0;
|
||||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
let header = HeaderBuilder::with_parent(&genesis())
|
||||||
sealed_empty_step(&validators, &genesis().compute_hash(), 44),
|
.empty_steps(&[(&validator(2), GENESIS_STEP + 2), (&validator(1), GENESIS_STEP + 1)])
|
||||||
sealed_empty_step(&validators, &genesis().compute_hash(), 43),
|
.step(GENESIS_STEP + 3)
|
||||||
]);
|
.sign_by(&validator(3));
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||||
|
|
||||||
// when empty steps are OK
|
// when empty steps are OK
|
||||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
let header = HeaderBuilder::with_parent(&genesis())
|
||||||
sealed_empty_step(&validators, &genesis().compute_hash(), 43),
|
.empty_steps(&[(&validator(1), GENESIS_STEP + 1), (&validator(2), GENESIS_STEP + 2)])
|
||||||
sealed_empty_step(&validators, &genesis().compute_hash(), 44),
|
.step(GENESIS_STEP + 3)
|
||||||
]);
|
.sign_by(&validator(3));
|
||||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,33 +633,19 @@ mod tests {
|
|||||||
config.validate_score_transition = 0;
|
config.validate_score_transition = 0;
|
||||||
|
|
||||||
// when chain score is invalid
|
// when chain score is invalid
|
||||||
let mut header = Header {
|
let header = HeaderBuilder::with_parent(&genesis())
|
||||||
seal: vec![vec![43].into(), vec![].into()],
|
.difficulty(100.into())
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
.sign_by(&validator(0));
|
||||||
parent_hash: genesis().compute_hash(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||||
|
|
||||||
// when chain score is accepted
|
// 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));
|
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_validator() {
|
fn verifies_validator() {
|
||||||
let validators = vec![validator(0), validator(1), validator(2)];
|
let good_header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(1));
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// when header author is invalid
|
// when header author is invalid
|
||||||
let mut header = good_header.clone();
|
let mut header = good_header.clone();
|
||||||
@@ -693,7 +665,7 @@ mod tests {
|
|||||||
fn pool_verifies_known_blocks() {
|
fn pool_verifies_known_blocks() {
|
||||||
// when header is known
|
// when header is known
|
||||||
assert_eq!(
|
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),
|
Err(Error::KnownHeader),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -703,7 +675,9 @@ mod tests {
|
|||||||
// when header number is less than finalized
|
// when header number is less than finalized
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| (
|
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,
|
None,
|
||||||
),),
|
),),
|
||||||
Err(Error::AncientHeader),
|
Err(Error::AncientHeader),
|
||||||
@@ -731,7 +705,7 @@ mod tests {
|
|||||||
fn pool_rejects_headers_with_redundant_receipts() {
|
fn pool_rejects_headers_with_redundant_receipts() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| (
|
default_accept_into_pool(|validators| (
|
||||||
block_i(4, validators),
|
HeaderBuilder::with_parent_number(3).sign_by_set(validators),
|
||||||
Some(vec![Receipt {
|
Some(vec![Receipt {
|
||||||
gas_used: 1.into(),
|
gas_used: 1.into(),
|
||||||
log_bloom: (&[0xff; 256]).into(),
|
log_bloom: (&[0xff; 256]).into(),
|
||||||
@@ -747,7 +721,7 @@ mod tests {
|
|||||||
fn pool_verifies_future_block_number() {
|
fn pool_verifies_future_block_number() {
|
||||||
// when header is too far from the future
|
// when header is too far from the future
|
||||||
assert_eq!(
|
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),
|
Err(Error::UnsignedTooFarInTheFuture),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -758,8 +732,9 @@ mod tests {
|
|||||||
// checks for DoubleVote
|
// checks for DoubleVote
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| (
|
default_accept_into_pool(|validators| (
|
||||||
custom_block_i(4, validators, |header| header.seal[0] =
|
HeaderBuilder::with_parent_number(3)
|
||||||
block_i(3, validators).seal[0].clone()),
|
.step(GENESIS_STEP + 3)
|
||||||
|
.sign_by_set(&validators),
|
||||||
None,
|
None,
|
||||||
),),
|
),),
|
||||||
Err(Error::DoubleVote),
|
Err(Error::DoubleVote),
|
||||||
@@ -772,21 +747,7 @@ mod tests {
|
|||||||
// (even if header will be considered invalid/duplicate later, we can use this signature
|
// (even if header will be considered invalid/duplicate later, we can use this signature
|
||||||
// as a proof of malicious action by this validator)
|
// as a proof of malicious action by this validator)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| (
|
default_accept_into_pool(|_| (HeaderBuilder::with_number(8).step(8).sign_by(&validator(1)), None,)),
|
||||||
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,
|
|
||||||
)),
|
|
||||||
Err(Error::NotValidator),
|
Err(Error::NotValidator),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -796,7 +757,7 @@ mod tests {
|
|||||||
let mut hash = None;
|
let mut hash = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| {
|
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());
|
hash = Some(header.compute_hash());
|
||||||
(header, None)
|
(header, None)
|
||||||
}),
|
}),
|
||||||
@@ -814,32 +775,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pool_verifies_header_with_unknown_parent() {
|
fn pool_verifies_header_with_unknown_parent() {
|
||||||
let mut hash = None;
|
let mut id = None;
|
||||||
|
let mut parent_id = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| {
|
default_accept_into_pool(|validators| {
|
||||||
let header = signed_header(
|
let header = HeaderBuilder::with_number(5)
|
||||||
validators,
|
.step(GENESIS_STEP + 5)
|
||||||
Header {
|
.sign_by_set(validators);
|
||||||
author: validators[2].address().as_fixed_bytes().into(),
|
id = Some(header.compute_id());
|
||||||
seal: vec![vec![47].into(), vec![].into()],
|
parent_id = header.parent_id();
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
|
||||||
parent_hash: [42; 32].into(),
|
|
||||||
number: 5,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
47,
|
|
||||||
);
|
|
||||||
hash = Some(header.compute_hash());
|
|
||||||
(header, None)
|
(header, None)
|
||||||
}),
|
}),
|
||||||
Ok((
|
Ok((
|
||||||
// parent tag required
|
// parent tag required
|
||||||
vec![(4u64, [42u8; 32]).encode(),],
|
vec![parent_id.unwrap().encode()],
|
||||||
// header provides two tags
|
// header provides two tags
|
||||||
vec![
|
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||||
(5u64, validators_addresses(3)[2]).encode(),
|
|
||||||
(5u64, hash.unwrap()).encode(),
|
|
||||||
],
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -852,55 +803,36 @@ mod tests {
|
|||||||
change_validators_set_at(3, validators_addresses(1), None);
|
change_validators_set_at(3, validators_addresses(1), None);
|
||||||
|
|
||||||
// header is signed using wrong set
|
// header is signed using wrong set
|
||||||
let header = signed_header(
|
let header = HeaderBuilder::with_number(5)
|
||||||
actual_validators,
|
.step(GENESIS_STEP + 2)
|
||||||
Header {
|
.sign_by_set(actual_validators);
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
(header, None)
|
(header, None)
|
||||||
}),
|
}),
|
||||||
Err(Error::NotValidator),
|
Err(Error::NotValidator),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut hash = None;
|
let mut id = None;
|
||||||
|
let mut parent_id = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|actual_validators| {
|
default_accept_into_pool(|actual_validators| {
|
||||||
// change finalized set at parent header + signal valid set at parent block
|
// change finalized set at parent header + signal valid set at parent block
|
||||||
change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3)));
|
change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3)));
|
||||||
|
|
||||||
// header is signed using wrong set
|
// header is signed using wrong set
|
||||||
let header = signed_header(
|
let header = HeaderBuilder::with_number(5)
|
||||||
actual_validators,
|
.step(GENESIS_STEP + 2)
|
||||||
Header {
|
.sign_by_set(actual_validators);
|
||||||
author: actual_validators[2].address().as_fixed_bytes().into(),
|
id = Some(header.compute_id());
|
||||||
seal: vec![vec![47].into(), vec![].into()],
|
parent_id = header.parent_id();
|
||||||
gas_limit: test_aura_config().min_gas_limit,
|
|
||||||
parent_hash: [42; 32].into(),
|
|
||||||
number: 5,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
47,
|
|
||||||
);
|
|
||||||
hash = Some(header.compute_hash());
|
|
||||||
|
|
||||||
(header, None)
|
(header, None)
|
||||||
}),
|
}),
|
||||||
Ok((
|
Ok((
|
||||||
// parent tag required
|
// parent tag required
|
||||||
vec![(4u64, [42u8; 32]).encode(),],
|
vec![parent_id.unwrap().encode(),],
|
||||||
// header provides two tags
|
// header provides two tags
|
||||||
vec![
|
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
|
||||||
(5u64, validators_addresses(3)[2]).encode(),
|
|
||||||
(5u64, hash.unwrap()).encode(),
|
|
||||||
],
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -909,9 +841,9 @@ mod tests {
|
|||||||
fn pool_rejects_headers_with_invalid_receipts() {
|
fn pool_rejects_headers_with_invalid_receipts() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| {
|
default_accept_into_pool(|validators| {
|
||||||
let header = custom_block_i(4, &validators, |header| {
|
let header = HeaderBuilder::with_parent_number(3)
|
||||||
header.log_bloom = (&[0xff; 256]).into();
|
.log_bloom((&[0xff; 256]).into())
|
||||||
});
|
.sign_by_set(validators);
|
||||||
(header, Some(vec![validators_change_recept(Default::default())]))
|
(header, Some(vec![validators_change_recept(Default::default())]))
|
||||||
}),
|
}),
|
||||||
Err(Error::TransactionsReceiptsMismatch),
|
Err(Error::TransactionsReceiptsMismatch),
|
||||||
@@ -923,12 +855,14 @@ mod tests {
|
|||||||
let mut hash = None;
|
let mut hash = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_accept_into_pool(|validators| {
|
default_accept_into_pool(|validators| {
|
||||||
let header = custom_block_i(4, &validators, |header| {
|
let header = HeaderBuilder::with_parent_number(3)
|
||||||
header.log_bloom = (&[0xff; 256]).into();
|
.log_bloom((&[0xff; 256]).into())
|
||||||
header.receipts_root = "81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45"
|
.receipts_root(
|
||||||
.parse()
|
"81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45"
|
||||||
.unwrap();
|
.parse()
|
||||||
});
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.sign_by_set(validators);
|
||||||
hash = Some(header.compute_hash());
|
hash = Some(header.compute_hash());
|
||||||
(header, Some(vec![validators_change_recept(Default::default())]))
|
(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 }
|
triehash = { version = "0.8.2", default-features = false }
|
||||||
plain_hasher = { version = "0.2.2", default-features = false }
|
plain_hasher = { version = "0.2.2", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
hex-literal = "0.2"
|
|
||||||
|
|
||||||
# Substrate Based Dependencies
|
# Substrate Based Dependencies
|
||||||
[dependencies.sp-api]
|
[dependencies.sp-api]
|
||||||
version = "2.0.0-rc3"
|
version = "2.0.0-rc3"
|
||||||
@@ -48,9 +45,18 @@ default-features = false
|
|||||||
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
rev = "606c56d2e2f69f68f3947551224be6a3515dff60"
|
||||||
git = "https://github.com/paritytech/substrate.git"
|
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]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
test-helpers = []
|
test-helpers = ["libsecp256k1"]
|
||||||
std = [
|
std = [
|
||||||
"serde/std",
|
"serde/std",
|
||||||
"serde-big-array",
|
"serde-big-array",
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
|
|
||||||
pub use parity_bytes::Bytes;
|
pub use parity_bytes::Bytes;
|
||||||
pub use primitive_types::{H160, H256, H512, U128, U256};
|
pub use primitive_types::{H160, H256, H512, U128, U256};
|
||||||
|
|
||||||
#[cfg(feature = "test-helpers")]
|
|
||||||
pub use rlp::encode as rlp_encode;
|
pub use rlp::encode as rlp_encode;
|
||||||
|
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
@@ -49,6 +47,9 @@ pub type RawTransaction = Vec<u8>;
|
|||||||
/// An ethereum address.
|
/// An ethereum address.
|
||||||
pub type Address = H160;
|
pub type Address = H160;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "test-helpers", test))]
|
||||||
|
pub mod signatures;
|
||||||
|
|
||||||
/// Complete header id.
|
/// Complete header id.
|
||||||
#[derive(Encode, Decode, Default, RuntimeDebug, PartialEq, Clone, Copy)]
|
#[derive(Encode, Decode, Default, RuntimeDebug, PartialEq, Clone, Copy)]
|
||||||
pub struct HeaderId {
|
pub struct HeaderId {
|
||||||
@@ -59,8 +60,8 @@ pub struct HeaderId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An Aura header.
|
/// An Aura header.
|
||||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||||
#[cfg_attr(feature = "std", derive(Default, Serialize, Deserialize))]
|
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
/// Parent block hash.
|
/// Parent block hash.
|
||||||
pub parent_hash: H256,
|
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)
|
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! {
|
sp_api::decl_runtime_apis! {
|
||||||
/// API for headers submitters.
|
/// API for headers submitters.
|
||||||
pub trait EthereumHeadersApi {
|
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