Files
pezkuwi-subxt/bridges/modules/ethereum/src/verification.rs
T
Hernando Castano 30844b1e9c Allow Multiple Bridge Pallet Instances (#226)
* Add Instance type parameter to pallet

* Sketch out what the runtime could look like

* Allow runtime to compile with multiple bridge pallets

* Cargo Fmt

* Allow an instance of a PoA chain to be used with currency-exchange

I specify that it's only _an instance_ instead of _instances_ since the currency-exchange
pallet does not support multiple instances itself. What this commit does is make it so
that the different instances of the PoA chains we currently have are compatible with the
currency-exchange pallet through the implementation of the PeerBlockchain trait.

* Add Instance type parameter to Currency Exchange pallet

* Wire up currency exchange intances in runtime

* Rust Fmt

* Show sccache

* Allow Eth pallet to use a default instance

* Use a default instance in Eth pallet tests

* Remove Rialto and Kovan feature flags

Through some discussions it has been decided that the `bridge-node` should, like
Substrate's `node-template`, be a showcase of the different pallets available in
a project. Because of this I've removed the feature flags for the Rialto and Kovan
networks in favour of having both of them included in the runtime.

* Update the chain_spec to use both Rialto and Kovan configs

* Update pallet level calls used by Substrate client

Allows the project to compile. However, it should be noted that in reality
we shouldn't be hardcoding the pallet we're calling.

* Allow currency-exchange pallet to use a default instance

* Support benchmarking an instance of the Eth pallet

* Update currency exchange benchmarks to work with instances

* Fix test helpers which now need a PoA instance

* Remove Actions for checking Rialto and Kovan features

* Add missing comments

* Update Runtime API string constants

* Add issue number for generic chain support in relay

* Add Runtime APIs for instances of the currency-exchange pallet

* Rust Fmt

Co-authored-by: Denis S. Soldatov aka General-Beck <general.beck@gmail.com>
2024-04-10 10:28:37 +02:00

886 lines
30 KiB
Rust

// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::error::Error;
use crate::validators::{Validators, ValidatorsConfiguration};
use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage};
use codec::Encode;
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_runtime::transaction_validity::TransactionTag;
use sp_std::{vec, vec::Vec};
/// Pre-check to see if should try and import this header.
/// Returns error if we should not try to import this block.
/// Returns ID of passed header and best finalized header.
pub fn is_importable_header<S: Storage>(storage: &S, header: &Header) -> Result<(HeaderId, HeaderId), Error> {
// we never import any header that competes with finalized header
let finalized_id = storage.finalized_block();
if header.number <= finalized_id.number {
return Err(Error::AncientHeader);
}
// we never import any header with known hash
let id = header.compute_id();
if storage.header(&id.hash).is_some() {
return Err(Error::KnownHeader);
}
Ok((id, finalized_id))
}
/// Try accept unsigned aura header into transaction pool.
///
/// Returns required and provided tags.
pub fn accept_aura_header_into_pool<S: Storage>(
storage: &S,
config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
pool_config: &PoolConfiguration,
header: &Header,
receipts: Option<&Vec<Receipt>>,
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
// check if we can verify further
let (header_id, _) = is_importable_header(storage, header)?;
// we can always do contextless checks
contextless_checks(config, header)?;
// we want to avoid having same headers twice in the pool
// => we're strict about receipts here - if we need them, we require receipts to be Some,
// otherwise we require receipts to be None
let receipts_required = Validators::new(validators_config).maybe_signals_validators_change(header);
match (receipts_required, receipts.is_some()) {
(true, false) => return Err(Error::MissingTransactionsReceipts),
(false, true) => return Err(Error::RedundantTransactionsReceipts),
_ => (),
}
// we do not want to have all future headers in the pool at once
// => if we see header with number > maximal ever seen header number + LIMIT,
// => we consider this transaction invalid, but only at this moment (we do not want to ban it)
// => let's mark it as Unknown transaction
let (best_id, _) = storage.best_block();
let difference = header.number.saturating_sub(best_id.number);
if difference > pool_config.max_future_number_difference {
return Err(Error::UnsignedTooFarInTheFuture);
}
// TODO: only accept new headers when we're at the tip of PoA chain
// https://github.com/paritytech/parity-bridges-common/issues/38
// we want to see at most one header with given number from single authority
// => every header is providing tag (block_number + authority)
// => since only one tx in the pool can provide the same tag, they're auto-deduplicated
let provides_number_and_authority_tag = (header.number, header.author).encode();
// we want to see several 'future' headers in the pool at once, but we may not have access to
// previous headers here
// => we can at least 'verify' that headers comprise a chain by providing and requiring
// tag (header.number, header.hash)
let provides_header_number_and_hash_tag = header_id.encode();
// depending on whether parent header is available, we either perform full or 'shortened' check
let context = storage.import_context(None, &header.parent_hash);
let tags = match context {
Some(context) => {
let header_step = contextual_checks(config, &context, None, header)?;
validator_checks(config, &context.validators_set().validators, header, header_step)?;
// since our parent is already in the storage, we do not require it
// to be in the transaction pool
(
vec![],
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
)
}
None => {
// we know nothing about parent header
// => the best thing we can do is to believe that there are no forks in
// PoA chain AND that the header is produced either by previous, or next
// scheduled validators set change
let header_step = header.step().ok_or(Error::MissingStep)?;
let best_context = storage.import_context(None, &best_id.hash).expect(
"import context is None only when header is missing from the storage;\
best header is always in the storage; qed",
);
let validators_check_result =
validator_checks(config, &best_context.validators_set().validators, header, header_step);
if let Err(error) = validators_check_result {
find_next_validators_signal(storage, &best_context)
.ok_or_else(|| error)
.and_then(|next_validators| validator_checks(config, &next_validators, header, header_step))?;
}
// since our parent is missing from the storage, we **DO** require it
// to be in the transaction pool
// (- 1 can't underflow because there's always best block in the header)
let requires_header_number_and_hash_tag = HeaderId {
number: header.number - 1,
hash: header.parent_hash,
}
.encode();
(
vec![requires_header_number_and_hash_tag],
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
)
}
};
// the heaviest, but rare operation - we do not want invalid receipts in the pool
if let Some(receipts) = receipts {
frame_support::debug::trace!(target: "runtime", "Got receipts! {:?}", receipts);
if !header.verify_receipts_root(receipts) {
return Err(Error::TransactionsReceiptsMismatch);
}
}
Ok(tags)
}
/// Verify header by Aura rules.
pub fn verify_aura_header<S: Storage>(
storage: &S,
config: &AuraConfiguration,
submitter: Option<S::Submitter>,
header: &Header,
) -> Result<ImportContext<S::Submitter>, Error> {
// let's do the lightest check first
contextless_checks(config, header)?;
// the rest of checks requires access to the parent header
let context = storage.import_context(submitter, &header.parent_hash).ok_or_else(|| {
frame_support::debug::warn!(
target: "runtime",
"Missing parent PoA block: ({:?}, {})",
header.number.checked_sub(1),
header.parent_hash,
);
Error::MissingParentBlock
})?;
let header_step = contextual_checks(config, &context, None, header)?;
validator_checks(config, &context.validators_set().validators, header, header_step)?;
Ok(context)
}
/// Perform basic checks that only require header itself.
fn contextless_checks(config: &AuraConfiguration, header: &Header) -> Result<(), Error> {
let expected_seal_fields = expected_header_seal_fields(config, header);
if header.seal.len() != expected_seal_fields {
return Err(Error::InvalidSealArity);
}
if header.number >= u64::max_value() {
return Err(Error::RidiculousNumber);
}
if header.gas_used > header.gas_limit {
return Err(Error::TooMuchGasUsed);
}
if header.gas_limit < config.min_gas_limit {
return Err(Error::InvalidGasLimit);
}
if header.gas_limit > config.max_gas_limit {
return Err(Error::InvalidGasLimit);
}
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
return Err(Error::ExtraDataOutOfBounds);
}
// we can't detect if block is from future in runtime
// => let's only do an overflow check
if header.timestamp > i32::max_value() as u64 {
return Err(Error::TimestampOverflow);
}
Ok(())
}
/// Perform checks that require access to parent header.
fn contextual_checks<Submitter>(
config: &AuraConfiguration,
context: &ImportContext<Submitter>,
validators_override: Option<&[Address]>,
header: &Header,
) -> Result<u64, Error> {
let validators = validators_override.unwrap_or_else(|| &context.validators_set().validators);
let header_step = header.step().ok_or(Error::MissingStep)?;
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
// Ensure header is from the step after context.
if header_step == parent_step || (header.number >= config.validate_step_transition && header_step <= parent_step) {
return Err(Error::DoubleVote);
}
// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
let empty_steps_len = match header.number >= config.empty_steps_transition {
true => {
let strict_empty_steps = header.number >= config.strict_empty_steps_transition;
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
let empty_steps_len = empty_steps.len();
let mut prev_empty_step = 0;
for empty_step in empty_steps {
if empty_step.step <= parent_step || empty_step.step >= header_step {
return Err(Error::InsufficientProof);
}
if !verify_empty_step(&header.parent_hash, &empty_step, validators) {
return Err(Error::InsufficientProof);
}
if strict_empty_steps {
if empty_step.step <= prev_empty_step {
return Err(Error::InsufficientProof);
}
prev_empty_step = empty_step.step;
}
}
empty_steps_len
}
false => 0,
};
// Validate chain score.
if header.number >= config.validate_score_transition {
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
if header.difficulty != expected_difficulty {
return Err(Error::InvalidDifficulty);
}
}
Ok(header_step)
}
/// Check that block is produced by expected validator.
fn validator_checks(
config: &AuraConfiguration,
validators: &[Address],
header: &Header,
header_step: u64,
) -> Result<(), Error> {
let expected_validator = *step_validator(validators, header_step);
if header.author != expected_validator {
return Err(Error::NotValidator);
}
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
let header_seal_hash = header
.seal_hash(header.number >= config.empty_steps_transition)
.ok_or(Error::MissingEmptySteps)?;
let is_invalid_proposer = !verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
if is_invalid_proposer {
return Err(Error::NotValidator);
}
Ok(())
}
/// Returns expected number of seal fields in the header.
fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> usize {
if header.number != u64::max_value() && header.number >= config.empty_steps_transition {
3
} else {
2
}
}
/// Verify single sealed empty step.
fn verify_empty_step(parent_hash: &H256, step: &SealedEmptyStep, validators: &[Address]) -> bool {
let expected_validator = *step_validator(validators, step.step);
let message = step.message(parent_hash);
verify_signature(&expected_validator, &step.signature, &message)
}
/// Chain scoring: total weight is sqrt(U256::max_value())*height - step
pub(crate) fn calculate_score(parent_step: u64, current_step: u64, current_empty_steps: usize) -> U256 {
U256::from(U128::max_value()) + U256::from(parent_step) - U256::from(current_step) + U256::from(current_empty_steps)
}
/// Verify that the signature over message has been produced by given validator.
fn verify_signature(expected_validator: &Address, signature: &H520, message: &H256) -> bool {
secp256k1_ecdsa_recover(signature.as_fixed_bytes(), message.as_fixed_bytes())
.map(|public| public_to_address(&public))
.map(|address| *expected_validator == address)
.unwrap_or(false)
}
/// Find next unfinalized validators set change after finalized set.
fn find_next_validators_signal<S: Storage>(storage: &S, context: &ImportContext<S::Submitter>) -> Option<Vec<Address>> {
// that's the earliest block number we may met in following loop
// it may be None if that's the first set
let best_set_signal_block = context.validators_set().signal_block;
// if parent schedules validators set change, then it may be our set
// else we'll start with last known change
let mut current_set_signal_block = context.last_signal_block();
let mut next_scheduled_set: Option<ScheduledChange> = None;
loop {
// if we have reached block that signals finalized change, then
// next_current_block_hash points to the block that schedules next
// change
let current_scheduled_set = match current_set_signal_block {
Some(current_set_signal_block) if Some(&current_set_signal_block) == best_set_signal_block.as_ref() => {
return next_scheduled_set.map(|scheduled_set| scheduled_set.validators)
}
None => return next_scheduled_set.map(|scheduled_set| scheduled_set.validators),
Some(current_set_signal_block) => storage.scheduled_change(&current_set_signal_block.hash).expect(
"header that is associated with this change is not pruned;\
scheduled changes are only removed when header is pruned; qed",
),
};
current_set_signal_block = current_scheduled_set.prev_signal_block;
next_scheduled_set = Some(current_scheduled_set);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{
insert_header, run_test_with_genesis, test_aura_config, validator, validator_address, validators_addresses,
validators_change_receipt, AccountId, HeaderBuilder, TestRuntime, GAS_LIMIT,
};
use crate::validators::ValidatorsSource;
use crate::DefaultInstance;
use crate::{
pool_configuration, BridgeStorage, FinalizedBlock, Headers, HeadersByNumber, NextValidatorsSetId,
ScheduledChanges, ValidatorsSet, ValidatorsSets,
};
use frame_support::{StorageMap, StorageValue};
use primitives::{compute_merkle_root, rlp_encode, TransactionOutcome, H520};
use secp256k1::SecretKey;
use sp_runtime::transaction_validity::TransactionTag;
const GENESIS_STEP: u64 = 42;
const TOTAL_VALIDATORS: usize = 3;
fn genesis() -> Header {
HeaderBuilder::genesis().step(GENESIS_STEP).sign_by(&validator(0))
}
fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result<ImportContext<AccountId>, Error> {
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
let storage = BridgeStorage::<TestRuntime>::new();
verify_aura_header(&storage, &config, None, header)
})
}
fn default_verify(header: &Header) -> Result<ImportContext<AccountId>, Error> {
verify_with_config(&test_aura_config(), header)
}
fn default_accept_into_pool(
mut make_header: impl FnMut(&[SecretKey]) -> (Header, Option<Vec<Receipt>>),
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
let validators = vec![validator(0), validator(1), validator(2)];
let mut storage = BridgeStorage::<TestRuntime>::new();
let block1 = HeaderBuilder::with_parent_number(0).sign_by_set(&validators);
insert_header(&mut storage, block1);
let block2 = HeaderBuilder::with_parent_number(1).sign_by_set(&validators);
let block2_id = block2.compute_id();
insert_header(&mut storage, block2);
let block3 = HeaderBuilder::with_parent_number(2).sign_by_set(&validators);
insert_header(&mut storage, block3);
FinalizedBlock::<DefaultInstance>::put(block2_id);
let validators_config =
ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
let (header, receipts) = make_header(&validators);
accept_aura_header_into_pool(
&storage,
&test_aura_config(),
&validators_config,
&pool_configuration(),
&header,
receipts.as_ref(),
)
})
}
fn change_validators_set_at(number: u64, finalized_set: Vec<Address>, signalled_set: Option<Vec<Address>>) {
let set_id = NextValidatorsSetId::<DefaultInstance>::get();
NextValidatorsSetId::<DefaultInstance>::put(set_id + 1);
ValidatorsSets::<DefaultInstance>::insert(
set_id,
ValidatorsSet {
validators: finalized_set,
signal_block: None,
enact_block: HeaderId {
number: 0,
hash: HeadersByNumber::<DefaultInstance>::get(&0).unwrap()[0],
},
},
);
let header_hash = HeadersByNumber::<DefaultInstance>::get(&number).unwrap()[0];
let mut header = Headers::<TestRuntime>::get(&header_hash).unwrap();
header.next_validators_set_id = set_id;
if let Some(signalled_set) = signalled_set {
header.last_signal_block = Some(HeaderId {
number: header.header.number - 1,
hash: header.header.parent_hash,
});
ScheduledChanges::<DefaultInstance>::insert(
header.header.parent_hash,
ScheduledChange {
validators: signalled_set,
prev_signal_block: None,
},
);
}
Headers::<TestRuntime>::insert(header_hash, header);
}
#[test]
fn verifies_seal_count() {
// when there are no seals at all
let mut header = Header::default();
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's single seal (we expect 2 or 3 seals)
header.seal = vec![vec![]];
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's 3 seals (we expect 2 by default)
header.seal = vec![vec![], vec![], vec![]];
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's 2 seals
header.seal = vec![vec![], vec![]];
assert_ne!(default_verify(&header), Err(Error::InvalidSealArity));
}
#[test]
fn verifies_header_number() {
// when number is u64::max_value()
let header = HeaderBuilder::with_number(u64::max_value()).sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
// when header is < u64::max_value()
let header = HeaderBuilder::with_number(u64::max_value() - 1).sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
}
#[test]
fn verifies_gas_used() {
// when gas used is larger than gas limit
let header = HeaderBuilder::with_number(1)
.gas_used((GAS_LIMIT + 1).into())
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
// when gas used is less than gas limit
let header = HeaderBuilder::with_number(1)
.gas_used((GAS_LIMIT - 1).into())
.sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
}
#[test]
fn verifies_gas_limit() {
let mut config = test_aura_config();
config.min_gas_limit = 100.into();
config.max_gas_limit = 200.into();
// when limit is lower than expected
let header = HeaderBuilder::with_number(1)
.gas_limit(50.into())
.sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
// when limit is larger than expected
let header = HeaderBuilder::with_number(1)
.gas_limit(250.into())
.sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
// when limit is within expected range
let header = HeaderBuilder::with_number(1)
.gas_limit(150.into())
.sign_by(&validator(0));
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
}
#[test]
fn verifies_extra_data_len() {
// when extra data is too large
let header = HeaderBuilder::with_number(1)
.extra_data(std::iter::repeat(42).take(1000).collect::<Vec<_>>())
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
// when extra data size is OK
let header = HeaderBuilder::with_number(1)
.extra_data(std::iter::repeat(42).take(10).collect::<Vec<_>>())
.sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
}
#[test]
fn verifies_timestamp() {
// when timestamp overflows i32
let header = HeaderBuilder::with_number(1)
.timestamp(i32::max_value() as u64 + 1)
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
// when timestamp doesn't overflow i32
let header = HeaderBuilder::with_number(1)
.timestamp(i32::max_value() as u64)
.sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::TimestampOverflow));
}
#[test]
fn verifies_parent_existence() {
// when there's no parent in the storage
let header = HeaderBuilder::with_number(1).sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
// when parent is in the storage
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
}
#[test]
fn verifies_step() {
// when step is missing from seals
let mut header = Header {
seal: vec![vec![], vec![]],
gas_limit: test_aura_config().min_gas_limit,
parent_hash: genesis().compute_hash(),
..Default::default()
};
assert_eq!(default_verify(&header), Err(Error::MissingStep));
// when step is the same as for the parent block
header.seal[0] = rlp_encode(&42u64);
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
// when step is OK
header.seal[0] = rlp_encode(&43u64);
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
// now check with validate_step check enabled
let mut config = test_aura_config();
config.validate_step_transition = 0;
// when step is lesser that for the parent block
header.seal[0] = rlp_encode(&40u64);
header.seal = vec![vec![40], vec![]];
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
// when step is OK
header.seal[0] = rlp_encode(&44u64);
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
}
#[test]
fn verifies_empty_step() {
let mut config = test_aura_config();
config.empty_steps_transition = 0;
// when empty step duplicates parent step
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(0), GENESIS_STEP)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when empty step signature check fails
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(100), GENESIS_STEP + 1)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when we are accepting strict empty steps and they come not in order
config.strict_empty_steps_transition = 0;
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(2), GENESIS_STEP + 2), (&validator(1), GENESIS_STEP + 1)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when empty steps are OK
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(1), GENESIS_STEP + 1), (&validator(2), GENESIS_STEP + 2)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
}
#[test]
fn verifies_chain_score() {
let mut config = test_aura_config();
config.validate_score_transition = 0;
// when chain score is invalid
let header = HeaderBuilder::with_parent(&genesis())
.difficulty(100.into())
.sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
// when chain score is accepted
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
}
#[test]
fn verifies_validator() {
let good_header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(1));
// when header author is invalid
let mut header = good_header.clone();
header.author = Default::default();
assert_eq!(default_verify(&header), Err(Error::NotValidator));
// when header signature is invalid
let mut header = good_header.clone();
header.seal[1] = rlp_encode(&H520::default());
assert_eq!(default_verify(&header), Err(Error::NotValidator));
// when everything is OK
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
}
#[test]
fn pool_verifies_known_blocks() {
// when header is known
assert_eq!(
default_accept_into_pool(|validators| (HeaderBuilder::with_parent_number(2).sign_by_set(validators), None)),
Err(Error::KnownHeader),
);
}
#[test]
fn pool_verifies_ancient_blocks() {
// when header number is less than finalized
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(1)
.gas_limit((GAS_LIMIT + 1).into())
.sign_by_set(validators),
None,
),),
Err(Error::AncientHeader),
);
}
#[test]
fn pool_rejects_headers_without_required_receipts() {
assert_eq!(
default_accept_into_pool(|_| (
Header {
number: 20_000_000,
seal: vec![vec![], vec![]],
gas_limit: test_aura_config().min_gas_limit,
log_bloom: (&[0xff; 256]).into(),
..Default::default()
},
None,
),),
Err(Error::MissingTransactionsReceipts),
);
}
#[test]
fn pool_rejects_headers_with_redundant_receipts() {
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(3).sign_by_set(validators),
Some(vec![Receipt {
gas_used: 1.into(),
log_bloom: (&[0xff; 256]).into(),
logs: vec![],
outcome: TransactionOutcome::Unknown,
}]),
),),
Err(Error::RedundantTransactionsReceipts),
);
}
#[test]
fn pool_verifies_future_block_number() {
// when header is too far from the future
assert_eq!(
default_accept_into_pool(|validators| (HeaderBuilder::with_number(100).sign_by_set(&validators), None),),
Err(Error::UnsignedTooFarInTheFuture),
);
}
#[test]
fn pool_performs_full_verification_when_parent_is_known() {
// if parent is known, then we'll execute contextual_checks, which
// checks for DoubleVote
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(3)
.step(GENESIS_STEP + 3)
.sign_by_set(&validators),
None,
),),
Err(Error::DoubleVote),
);
}
#[test]
fn pool_performs_validators_checks_when_parent_is_unknown() {
// if parent is unknown, then we still need to check if header has required signature
// (even if header will be considered invalid/duplicate later, we can use this signature
// as a proof of malicious action by this validator)
assert_eq!(
default_accept_into_pool(|_| (HeaderBuilder::with_number(8).step(8).sign_by(&validator(1)), None,)),
Err(Error::NotValidator),
);
}
#[test]
fn pool_verifies_header_with_known_parent() {
let mut hash = None;
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3).sign_by_set(validators);
hash = Some(header.compute_hash());
(header, None)
}),
Ok((
// no tags are required
vec![],
// header provides two tags
vec![
(4u64, validators_addresses(3)[1]).encode(),
(4u64, hash.unwrap()).encode(),
],
)),
);
}
#[test]
fn pool_verifies_header_with_unknown_parent() {
let mut id = None;
let mut parent_id = None;
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_number(5)
.step(GENESIS_STEP + 5)
.sign_by_set(validators);
id = Some(header.compute_id());
parent_id = header.parent_id();
(header, None)
}),
Ok((
// parent tag required
vec![parent_id.unwrap().encode()],
// header provides two tags
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
)),
);
}
#[test]
fn pool_uses_next_validators_set_when_finalized_fails() {
assert_eq!(
default_accept_into_pool(|actual_validators| {
// change finalized set at parent header
change_validators_set_at(3, validators_addresses(1), None);
// header is signed using wrong set
let header = HeaderBuilder::with_number(5)
.step(GENESIS_STEP + 2)
.sign_by_set(actual_validators);
(header, None)
}),
Err(Error::NotValidator),
);
let mut id = None;
let mut parent_id = None;
assert_eq!(
default_accept_into_pool(|actual_validators| {
// change finalized set at parent header + signal valid set at parent block
change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3)));
// header is signed using wrong set
let header = HeaderBuilder::with_number(5)
.step(GENESIS_STEP + 2)
.sign_by_set(actual_validators);
id = Some(header.compute_id());
parent_id = header.parent_id();
(header, None)
}),
Ok((
// parent tag required
vec![parent_id.unwrap().encode(),],
// header provides two tags
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
)),
);
}
#[test]
fn pool_rejects_headers_with_invalid_receipts() {
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3)
.log_bloom((&[0xff; 256]).into())
.sign_by_set(validators);
(header, Some(vec![validators_change_receipt(Default::default())]))
}),
Err(Error::TransactionsReceiptsMismatch),
);
}
#[test]
fn pool_accepts_headers_with_valid_receipts() {
let mut hash = None;
let receipts = vec![validators_change_receipt(Default::default())];
let receipts_root = compute_merkle_root(receipts.iter().map(|r| r.rlp()));
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3)
.log_bloom((&[0xff; 256]).into())
.receipts_root(receipts_root)
.sign_by_set(validators);
hash = Some(header.compute_hash());
(header, Some(receipts.clone()))
}),
Ok((
// no tags are required
vec![],
// header provides two tags
vec![
(4u64, validators_addresses(3)[1]).encode(),
(4u64, hash.unwrap()).encode(),
],
)),
);
}
}