openethereum/ethcore/src/verification/verification.rs

525 lines
19 KiB
Rust
Raw Normal View History

2016-02-05 13:40:41 +01:00
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity 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 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. If not, see <http://www.gnu.org/licenses/>.
2016-01-10 21:30:22 +01:00
/// Block and transaction verification functions
///
/// Block verification is done in 3 steps
/// 1. Quick verification upon adding to the block queue
/// 2. Signatures verification done in the queue.
/// 3. Final verification against the blockchain done before enactment.
use common::*;
use engines::Engine;
2016-01-12 13:14:01 +01:00
use blockchain::*;
2016-09-01 14:29:59 +02:00
use rlp::{UntrustedRlp, View};
2016-01-09 19:10:05 +01:00
2016-01-17 23:07:58 +01:00
/// Preprocessed block data gathered in `verify_block_unordered` call
pub struct PreverifiedBlock {
2016-01-17 23:07:58 +01:00
/// Populated block header
pub header: Header,
/// Populated block transactions
2016-02-04 17:23:53 +01:00
pub transactions: Vec<SignedTransaction>,
2016-01-17 23:07:58 +01:00
/// Block bytes
pub bytes: Bytes,
}
impl HeapSizeOf for PreverifiedBlock {
fn heap_size_of_children(&self) -> usize {
self.header.heap_size_of_children()
+ self.transactions.heap_size_of_children()
+ self.bytes.heap_size_of_children()
}
}
2016-01-10 21:30:22 +01:00
/// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block
2016-01-15 12:26:04 +01:00
pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> {
try!(verify_header_params(&header, engine));
try!(verify_block_integrity(bytes, &header.transactions_root(), &header.uncles_hash()));
try!(engine.verify_block_basic(&header, Some(bytes)));
for u in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::<Header>()) {
let u = try!(u);
try!(verify_header_params(&u, engine));
try!(engine.verify_block_basic(&u, None));
}
2016-01-16 18:30:27 +01:00
// Verify transactions.
// TODO: either use transaction views or cache the decoded transactions.
let v = BlockView::new(bytes);
for t in v.transactions() {
try!(engine.verify_transaction_basic(&t, &header));
}
Ok(())
}
2016-01-09 19:10:05 +01:00
2016-01-10 21:30:22 +01:00
/// Phase 2 verification. Perform costly checks such as transaction signatures and block nonce for ethash.
/// Still operates on a individual block
2016-04-06 10:07:24 +02:00
/// Returns a `PreverifiedBlock` structure populated with transactions
pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine) -> Result<PreverifiedBlock, Error> {
2016-01-17 23:07:58 +01:00
try!(engine.verify_block_unordered(&header, Some(&bytes)));
for u in try!(UntrustedRlp::new(&bytes).at(2)).iter().map(|rlp| rlp.as_val::<Header>()) {
try!(engine.verify_block_unordered(&try!(u), None));
}
2016-03-04 11:56:04 +01:00
// Verify transactions.
2016-01-17 23:07:58 +01:00
let mut transactions = Vec::new();
{
let v = BlockView::new(&bytes);
for t in v.transactions() {
try!(engine.verify_transaction(&t, &header));
transactions.push(t);
}
2016-01-16 18:30:27 +01:00
}
Ok(PreverifiedBlock {
2016-01-17 23:07:58 +01:00
header: header,
transactions: transactions,
bytes: bytes,
})
2016-01-09 19:10:05 +01:00
}
2016-01-11 13:51:58 +01:00
/// Phase 3 verification. Check block information against parent and uncles.
2016-03-05 10:45:05 +01:00
pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error> {
2016-01-14 01:28:37 +01:00
// TODO: verify timestamp
let parent = try!(bc.block_header(&header.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownParent(header.parent_hash().clone()))));
try!(verify_parent(&header, &parent));
2016-01-14 19:03:48 +01:00
try!(engine.verify_block_family(&header, &parent, Some(bytes)));
let num_uncles = try!(UntrustedRlp::new(bytes).at(2)).item_count();
if num_uncles != 0 {
2016-01-10 21:30:22 +01:00
if num_uncles > engine.maximum_uncle_count() {
return Err(From::from(BlockError::TooManyUncles(OutOfBounds { min: None, max: Some(engine.maximum_uncle_count()), found: num_uncles })));
}
let mut excluded = HashSet::new();
excluded.insert(header.hash());
let mut hash = header.parent_hash().clone();
excluded.insert(hash.clone());
for _ in 0..engine.maximum_uncle_age() {
match bc.block_details(&hash) {
Some(details) => {
excluded.insert(details.parent.clone());
let b = bc.block(&hash).unwrap();
excluded.extend(BlockView::new(&b).uncle_hashes());
hash = details.parent;
}
None => break
}
}
for uncle in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::<Header>()) {
let uncle = try!(uncle);
2016-01-12 13:14:01 +01:00
if excluded.contains(&uncle.hash()) {
return Err(From::from(BlockError::UncleInChain(uncle.hash())))
}
// m_currentBlock.number() - uncle.number() m_cB.n - uP.n()
// 1 2
// 2
// 3
// 4
// 5
// 6 7
// (8 Invalid)
let depth = if header.number() > uncle.number() { header.number() - uncle.number() } else { 0 };
if depth > engine.maximum_uncle_age() as u64 {
return Err(From::from(BlockError::UncleTooOld(OutOfBounds { min: Some(header.number() - depth), max: Some(header.number() - 1), found: uncle.number() })));
}
else if depth < 1 {
return Err(From::from(BlockError::UncleIsBrother(OutOfBounds { min: Some(header.number() - depth), max: Some(header.number() - 1), found: uncle.number() })));
}
// cB
// cB.p^1 1 depth, valid uncle
// cB.p^2 ---/ 2
// cB.p^3 -----/ 3
// cB.p^4 -------/ 4
// cB.p^5 ---------/ 5
// cB.p^6 -----------/ 6
// cB.p^7 -------------/
// cB.p^8
let mut expected_uncle_parent = header.parent_hash().clone();
let uncle_parent = try!(bc.block_header(&uncle.parent_hash()).ok_or_else(|| Error::from(BlockError::UnknownUncleParent(uncle.parent_hash().clone()))));
for _ in 0..depth {
2016-01-12 13:14:01 +01:00
match bc.block_details(&expected_uncle_parent) {
2016-03-04 11:56:04 +01:00
Some(details) => {
2016-01-12 13:14:01 +01:00
expected_uncle_parent = details.parent;
},
None => break
}
}
if expected_uncle_parent != uncle_parent.hash() {
return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash())));
}
2016-01-12 13:14:01 +01:00
try!(verify_parent(&uncle, &uncle_parent));
2016-01-14 19:03:48 +01:00
try!(engine.verify_block_family(&uncle, &uncle_parent, Some(bytes)));
}
}
2016-01-09 19:10:05 +01:00
Ok(())
}
2016-01-10 21:30:22 +01:00
2016-01-14 19:03:48 +01:00
/// Phase 4 verification. Check block information against transaction enactment results,
pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error> {
if expected.gas_used() != got.gas_used() {
return Err(From::from(BlockError::InvalidGasUsed(Mismatch { expected: expected.gas_used().clone(), found: got.gas_used().clone() })))
2016-01-14 19:03:48 +01:00
}
if expected.log_bloom() != got.log_bloom() {
return Err(From::from(BlockError::InvalidLogBloom(Mismatch { expected: expected.log_bloom().clone(), found: got.log_bloom().clone() })))
2016-01-14 19:03:48 +01:00
}
if expected.state_root() != got.state_root() {
return Err(From::from(BlockError::InvalidStateRoot(Mismatch { expected: expected.state_root().clone(), found: got.state_root().clone() })))
2016-01-16 01:44:07 +01:00
}
if expected.receipts_root() != got.receipts_root() {
return Err(From::from(BlockError::InvalidReceiptsRoot(Mismatch { expected: expected.receipts_root().clone(), found: got.receipts_root().clone() })))
2016-01-14 19:03:48 +01:00
}
Ok(())
}
2016-01-10 21:30:22 +01:00
/// Check basic header parameters.
pub fn verify_header_params(header: &Header, engine: &Engine) -> Result<(), Error> {
if header.number() >= From::from(BlockNumber::max_value()) {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { max: Some(From::from(BlockNumber::max_value())), min: None, found: header.number() })))
2016-01-10 21:30:22 +01:00
}
if header.gas_used() > header.gas_limit() {
return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit().clone()), min: None, found: header.gas_used().clone() })));
2016-01-10 21:30:22 +01:00
}
let min_gas_limit = engine.params().min_gas_limit;
if header.gas_limit() < &min_gas_limit {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: header.gas_limit().clone() })));
}
let maximum_extra_data_size = engine.maximum_extra_data_size();
if header.number() != 0 && header.extra_data().len() > maximum_extra_data_size {
return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: None, max: Some(maximum_extra_data_size), found: header.extra_data().len() })));
}
2016-01-10 21:30:22 +01:00
Ok(())
}
/// Check header parameters agains parent header.
fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> {
if !header.parent_hash().is_zero() && &parent.hash() != header.parent_hash() {
return Err(From::from(BlockError::InvalidParentHash(Mismatch { expected: parent.hash(), found: header.parent_hash().clone() })))
2016-01-10 21:30:22 +01:00
}
if header.timestamp() <= parent.timestamp() {
return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: None, min: Some(parent.timestamp() + 1), found: header.timestamp() })))
2016-01-10 21:30:22 +01:00
}
if header.number() != parent.number() + 1 {
return Err(From::from(BlockError::InvalidNumber(Mismatch { expected: parent.number() + 1, found: header.number() })));
2016-01-10 21:30:22 +01:00
}
Ok(())
}
/// Verify block data against header: transactions root and uncles hash.
fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &H256) -> Result<(), Error> {
let block = UntrustedRlp::new(block);
let tx = try!(block.at(1));
let expected_root = &ordered_trie_root(tx.iter().map(|r| r.as_raw().to_vec())); //TODO: get rid of vectors here
2016-01-10 21:30:22 +01:00
if expected_root != transactions_root {
return Err(From::from(BlockError::InvalidTransactionsRoot(Mismatch { expected: expected_root.clone(), found: transactions_root.clone() })))
}
let expected_uncles = &try!(block.at(2)).as_raw().sha3();
2016-01-10 21:30:22 +01:00
if expected_uncles != uncles_hash {
return Err(From::from(BlockError::InvalidUnclesHash(Mismatch { expected: expected_uncles.clone(), found: uncles_hash.clone() })))
}
Ok(())
}
2016-01-12 13:14:01 +01:00
#[cfg(test)]
mod tests {
use util::*;
use ethkey::{Random, Generator};
2016-01-12 13:14:01 +01:00
use header::*;
use verification::*;
use blockchain::extras::*;
2016-01-12 13:14:01 +01:00
use error::*;
use error::BlockError::*;
use views::*;
use blockchain::*;
use engines::Engine;
2016-01-17 12:55:00 +01:00
use spec::*;
use transaction::*;
2016-01-29 10:16:53 +01:00
use tests::helpers::*;
use types::log_entry::{LogEntry, LocalizedLogEntry};
2016-09-01 14:29:59 +02:00
use rlp::View;
2016-01-12 13:14:01 +01:00
fn check_ok(result: Result<(), Error>) {
result.unwrap_or_else(|e| panic!("Block verification failed: {:?}", e));
}
fn check_fail(result: Result<(), Error>, e: BlockError) {
match result {
Err(Error::Block(ref error)) if *error == e => (),
Err(other) => panic!("Block verification failed.\nExpected: {:?}\nGot: {:?}", e, other),
Ok(_) => panic!("Block verification failed.\nExpected: {:?}\nGot: Ok", e),
}
}
struct TestBlockChain {
blocks: HashMap<H256, Bytes>,
numbers: HashMap<BlockNumber, H256>,
}
2016-03-12 10:07:55 +01:00
impl Default for TestBlockChain {
fn default() -> Self {
TestBlockChain::new()
}
}
2016-01-12 13:14:01 +01:00
impl TestBlockChain {
2016-03-12 10:07:55 +01:00
pub fn new() -> Self {
2016-01-12 13:14:01 +01:00
TestBlockChain {
blocks: HashMap::new(),
numbers: HashMap::new(),
}
}
pub fn insert(&mut self, bytes: Bytes) {
let number = BlockView::new(&bytes).header_view().number();
let hash = BlockView::new(&bytes).header_view().sha3();
self.blocks.insert(hash.clone(), bytes);
self.numbers.insert(number, hash.clone());
}
}
impl BlockProvider for TestBlockChain {
fn is_known(&self, hash: &H256) -> bool {
self.blocks.contains_key(hash)
}
fn first_block(&self) -> Option<H256> {
unimplemented!()
}
2016-01-12 13:14:01 +01:00
/// Get raw block data
fn block(&self, hash: &H256) -> Option<Bytes> {
2016-01-19 13:47:30 +01:00
self.blocks.get(hash).cloned()
2016-01-12 13:14:01 +01:00
}
fn block_header_data(&self, hash: &H256) -> Option<Bytes> {
self.block(hash).map(|b| BlockView::new(&b).header_rlp().as_raw().to_vec())
}
fn block_body(&self, hash: &H256) -> Option<Bytes> {
self.block(hash).map(|b| BlockChain::block_to_body(&b))
}
fn best_ancient_block(&self) -> Option<H256> {
None
}
2016-01-12 13:14:01 +01:00
/// Get the familial details concerning a block.
fn block_details(&self, hash: &H256) -> Option<BlockDetails> {
self.blocks.get(hash).map(|bytes| {
let header = BlockView::new(bytes).header();
BlockDetails {
number: header.number(),
total_difficulty: header.difficulty().clone(),
parent: header.parent_hash().clone(),
2016-01-12 13:14:01 +01:00
children: Vec::new(),
}
})
}
2016-02-08 15:53:22 +01:00
fn transaction_address(&self, _hash: &H256) -> Option<TransactionAddress> {
unimplemented!()
}
2016-01-12 13:14:01 +01:00
/// Get the hash of given block's number.
fn block_hash(&self, index: BlockNumber) -> Option<H256> {
2016-01-19 13:47:30 +01:00
self.numbers.get(&index).cloned()
2016-01-12 13:14:01 +01:00
}
2016-02-15 15:42:43 +01:00
fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockNumber, _to_block: BlockNumber) -> Vec<BlockNumber> {
unimplemented!()
}
fn block_receipts(&self, _hash: &H256) -> Option<BlockReceipts> {
unimplemented!()
}
fn logs<F>(&self, _blocks: Vec<BlockNumber>, _matches: F, _limit: Option<usize>) -> Vec<LocalizedLogEntry>
where F: Fn(&LogEntry) -> bool, Self: Sized {
unimplemented!()
}
2016-01-12 13:14:01 +01:00
}
2016-01-15 12:26:04 +01:00
fn basic_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
let header = BlockView::new(bytes).header();
verify_block_basic(&header, bytes, engine)
}
fn family_test<BC>(bytes: &[u8], engine: &Engine, bc: &BC) -> Result<(), Error> where BC: BlockProvider {
let header = BlockView::new(bytes).header();
verify_block_family(&header, bytes, engine, bc)
}
2016-01-12 13:14:01 +01:00
#[test]
2016-04-06 10:07:24 +02:00
#[cfg_attr(feature="dev", allow(similar_names))]
2016-01-12 13:14:01 +01:00
fn test_verify_block() {
2016-09-01 14:29:59 +02:00
use rlp::{RlpStream, Stream};
2016-01-12 13:14:01 +01:00
// Test against morden
let mut good = Header::new();
2016-01-17 12:55:00 +01:00
let spec = Spec::new_test();
let engine = &*spec.engine;
2016-01-12 13:14:01 +01:00
let min_gas_limit = engine.params().min_gas_limit;
good.set_gas_limit(min_gas_limit);
good.set_timestamp(40);
good.set_number(10);
2016-01-12 13:14:01 +01:00
let keypair = Random.generate().unwrap();
2016-02-04 23:48:29 +01:00
let tr1 = Transaction {
action: Action::Create,
value: U256::from(0),
data: Bytes::new(),
gas: U256::from(30_000),
gas_price: U256::from(40_000),
nonce: U256::one()
}.sign(keypair.secret());
2016-02-04 23:48:29 +01:00
let tr2 = Transaction {
action: Action::Create,
value: U256::from(0),
data: Bytes::new(),
gas: U256::from(30_000),
gas_price: U256::from(40_000),
nonce: U256::from(2)
}.sign(keypair.secret());
2016-02-04 23:48:29 +01:00
let good_transactions = [ tr1.clone(), tr2.clone() ];
2016-01-12 13:14:01 +01:00
let diff_inc = U256::from(0x40);
let mut parent6 = good.clone();
parent6.set_number(6);
2016-01-12 13:14:01 +01:00
let mut parent7 = good.clone();
parent7.set_number(7);
parent7.set_parent_hash(parent6.hash());
parent7.set_difficulty(parent6.difficulty().clone() + diff_inc);
parent7.set_timestamp(parent6.timestamp() + 10);
2016-01-12 13:14:01 +01:00
let mut parent8 = good.clone();
parent8.set_number(8);
parent8.set_parent_hash(parent7.hash());
parent8.set_difficulty(parent7.difficulty().clone() + diff_inc);
parent8.set_timestamp(parent7.timestamp() + 10);
2016-01-12 13:14:01 +01:00
let mut good_uncle1 = good.clone();
good_uncle1.set_number(9);
good_uncle1.set_parent_hash(parent8.hash());
good_uncle1.set_difficulty(parent8.difficulty().clone() + diff_inc);
good_uncle1.set_timestamp(parent8.timestamp() + 10);
good_uncle1.extra_data_mut().push(1u8);
2016-01-12 13:14:01 +01:00
let mut good_uncle2 = good.clone();
good_uncle2.set_number(8);
good_uncle2.set_parent_hash(parent7.hash());
good_uncle2.set_difficulty(parent7.difficulty().clone() + diff_inc);
good_uncle2.set_timestamp(parent7.timestamp() + 10);
good_uncle2.extra_data_mut().push(2u8);
2016-01-12 13:14:01 +01:00
2016-01-12 13:43:43 +01:00
let good_uncles = vec![ good_uncle1.clone(), good_uncle2.clone() ];
2016-01-12 13:14:01 +01:00
let mut uncles_rlp = RlpStream::new();
uncles_rlp.append(&good_uncles);
let good_uncles_hash = uncles_rlp.as_raw().sha3();
let good_transactions_root = ordered_trie_root(good_transactions.iter().map(|t| ::rlp::encode::<SignedTransaction>(t).to_vec()));
2016-01-12 13:14:01 +01:00
let mut parent = good.clone();
parent.set_number(9);
parent.set_timestamp(parent8.timestamp() + 10);
parent.set_parent_hash(parent8.hash());
parent.set_difficulty(parent8.difficulty().clone() + diff_inc);
2016-01-12 13:14:01 +01:00
good.set_parent_hash(parent.hash());
good.set_difficulty(parent.difficulty().clone() + diff_inc);
good.set_timestamp(parent.timestamp() + 10);
2016-01-12 13:14:01 +01:00
let mut bc = TestBlockChain::new();
bc.insert(create_test_block(&good));
bc.insert(create_test_block(&parent));
bc.insert(create_test_block(&parent6));
bc.insert(create_test_block(&parent7));
bc.insert(create_test_block(&parent8));
check_ok(basic_test(&create_test_block(&good), engine));
2016-01-12 13:14:01 +01:00
let mut header = good.clone();
header.set_transactions_root(good_transactions_root.clone());
header.set_uncles_hash(good_uncles_hash.clone());
check_ok(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine));
2016-01-12 13:14:01 +01:00
header.set_gas_limit(min_gas_limit - From::from(1));
check_fail(basic_test(&create_test_block(&header), engine),
InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: header.gas_limit().clone() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_number(BlockNumber::max_value());
check_fail(basic_test(&create_test_block(&header), engine),
RidiculousNumber(OutOfBounds { max: Some(BlockNumber::max_value()), min: None, found: header.number() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
let gas_used = header.gas_limit().clone() + 1.into();
header.set_gas_used(gas_used);
check_fail(basic_test(&create_test_block(&header), engine),
TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit().clone()), min: None, found: header.gas_used().clone() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.extra_data_mut().resize(engine.maximum_extra_data_size() + 1, 0u8);
check_fail(basic_test(&create_test_block(&header), engine),
ExtraDataOutOfBounds(OutOfBounds { max: Some(engine.maximum_extra_data_size()), min: None, found: header.extra_data().len() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.extra_data_mut().resize(engine.maximum_extra_data_size() + 1, 0u8);
check_fail(basic_test(&create_test_block(&header), engine),
ExtraDataOutOfBounds(OutOfBounds { max: Some(engine.maximum_extra_data_size()), min: None, found: header.extra_data().len() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_uncles_hash(good_uncles_hash.clone());
check_fail(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine),
InvalidTransactionsRoot(Mismatch { expected: good_transactions_root.clone(), found: header.transactions_root().clone() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_transactions_root(good_transactions_root.clone());
check_fail(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine),
InvalidUnclesHash(Mismatch { expected: good_uncles_hash.clone(), found: header.uncles_hash().clone() }));
2016-01-12 13:14:01 +01:00
check_ok(family_test(&create_test_block(&good), engine, &bc));
check_ok(family_test(&create_test_block_with_data(&good, &good_transactions, &good_uncles), engine, &bc));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_parent_hash(H256::random());
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine, &bc),
UnknownParent(header.parent_hash().clone()));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_timestamp(10);
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine, &bc),
InvalidTimestamp(OutOfBounds { max: None, min: Some(parent.timestamp() + 1), found: header.timestamp() }));
2016-01-12 13:14:01 +01:00
header = good.clone();
header.set_number(9);
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine, &bc),
InvalidNumber(Mismatch { expected: parent.number() + 1, found: header.number() }));
2016-03-04 11:56:04 +01:00
2016-01-12 13:43:43 +01:00
header = good.clone();
let mut bad_uncles = good_uncles.clone();
bad_uncles.push(good_uncle1.clone());
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &bad_uncles), engine, &bc),
2016-01-12 13:43:43 +01:00
TooManyUncles(OutOfBounds { max: Some(engine.maximum_uncle_count()), min: None, found: bad_uncles.len() }));
// TODO: some additional uncle checks
2016-01-12 13:14:01 +01:00
}
}