Verification tests
This commit is contained in:
parent
c6509d7752
commit
a9a5d71f2f
@ -47,6 +47,55 @@ impl BestBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interface for querying blocks by hash and by number.
|
||||||
|
pub trait BlockProvider {
|
||||||
|
/// Returns true if the given block is known
|
||||||
|
/// (though not necessarily a part of the canon chain).
|
||||||
|
fn is_known(&self, hash: &H256) -> bool;
|
||||||
|
|
||||||
|
/// Get raw block data
|
||||||
|
fn block(&self, hash: &H256) -> Option<Bytes>;
|
||||||
|
|
||||||
|
/// Get the familial details concerning a block.
|
||||||
|
fn block_details(&self, hash: &H256) -> Option<BlockDetails>;
|
||||||
|
|
||||||
|
/// Get the hash of given block's number.
|
||||||
|
fn block_hash(&self, index: BlockNumber) -> Option<H256>;
|
||||||
|
|
||||||
|
/// Get the partial-header of a block.
|
||||||
|
fn block_header(&self, hash: &H256) -> Option<Header> {
|
||||||
|
self.block(hash).map(|bytes| BlockView::new(&bytes).header())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of uncles for a given block.
|
||||||
|
/// Returns None if block deos not exist.
|
||||||
|
fn uncles(&self, hash: &H256) -> Option<Vec<Header>> {
|
||||||
|
self.block(hash).map(|bytes| BlockView::new(&bytes).uncles())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of uncle hashes for a given block.
|
||||||
|
/// Returns None if block does not exist.
|
||||||
|
fn uncle_hashes(&self, hash: &H256) -> Option<Vec<H256>> {
|
||||||
|
self.block(hash).map(|bytes| BlockView::new(&bytes).uncle_hashes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of given block's hash.
|
||||||
|
fn block_number(&self, hash: &H256) -> Option<BlockNumber> {
|
||||||
|
self.block(hash).map(|bytes| BlockView::new(&bytes).header_view().number())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a list of transactions for a given block.
|
||||||
|
/// Returns None if block deos not exist.
|
||||||
|
fn transactions(&self, hash: &H256) -> Option<Vec<Transaction>> {
|
||||||
|
self.block(hash).map(|bytes| BlockView::new(&bytes).transactions())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to genesis hash.
|
||||||
|
fn genesis_hash(&self) -> H256 {
|
||||||
|
self.block_hash(0).expect("Genesis hash should always exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Structure providing fast access to blockchain data.
|
/// Structure providing fast access to blockchain data.
|
||||||
///
|
///
|
||||||
/// **Does not do input data verification.**
|
/// **Does not do input data verification.**
|
||||||
@ -67,6 +116,48 @@ pub struct BlockChain {
|
|||||||
blocks_db: DB
|
blocks_db: DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockProvider for BlockChain {
|
||||||
|
/// Returns true if the given block is known
|
||||||
|
/// (though not necessarily a part of the canon chain).
|
||||||
|
fn is_known(&self, hash: &H256) -> bool {
|
||||||
|
self.query_extras_exist(hash, &self.block_details)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get raw block data
|
||||||
|
fn block(&self, hash: &H256) -> Option<Bytes> {
|
||||||
|
{
|
||||||
|
let read = self.blocks.read().unwrap();
|
||||||
|
match read.get(hash) {
|
||||||
|
Some(v) => return Some(v.clone()),
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = self.blocks_db.get(hash)
|
||||||
|
.expect("Low level database error. Some issue with disk?");
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
Some(b) => {
|
||||||
|
let bytes: Bytes = b.to_vec();
|
||||||
|
let mut write = self.blocks.write().unwrap();
|
||||||
|
write.insert(hash.clone(), bytes.clone());
|
||||||
|
Some(bytes)
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the familial details concerning a block.
|
||||||
|
fn block_details(&self, hash: &H256) -> Option<BlockDetails> {
|
||||||
|
self.query_extras(hash, &self.block_details)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the hash of given block's number.
|
||||||
|
fn block_hash(&self, index: BlockNumber) -> Option<H256> {
|
||||||
|
self.query_extras(&index, &self.block_hashes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BlockChain {
|
impl BlockChain {
|
||||||
/// Create new instance of blockchain from given Genesis
|
/// Create new instance of blockchain from given Genesis
|
||||||
///
|
///
|
||||||
@ -363,61 +454,11 @@ impl BlockChain {
|
|||||||
(batch, Some(best_block))
|
(batch, Some(best_block))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the given block is known
|
|
||||||
/// (though not necessarily a part of the canon chain).
|
|
||||||
pub fn is_known(&self, hash: &H256) -> bool {
|
|
||||||
self.query_extras_exist(hash, &self.block_details)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if transaction is known.
|
/// Returns true if transaction is known.
|
||||||
pub fn is_known_transaction(&self, hash: &H256) -> bool {
|
pub fn is_known_transaction(&self, hash: &H256) -> bool {
|
||||||
self.query_extras_exist(hash, &self.transaction_addresses)
|
self.query_extras_exist(hash, &self.transaction_addresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns reference to genesis hash.
|
|
||||||
pub fn genesis_hash(&self) -> H256 {
|
|
||||||
self.block_hash(0).expect("Genesis hash should always exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the partial-header of a block.
|
|
||||||
pub fn block_header(&self, hash: &H256) -> Option<Header> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).header())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of transactions for a given block.
|
|
||||||
/// Returns None if block deos not exist.
|
|
||||||
pub fn transactions(&self, hash: &H256) -> Option<Vec<Transaction>> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).transactions())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of transaction hashes for a given block.
|
|
||||||
/// Returns None if block does not exist.
|
|
||||||
pub fn transaction_hashes(&self, hash: &H256) -> Option<Vec<H256>> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).transaction_hashes())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of uncles for a given block.
|
|
||||||
/// Returns None if block deos not exist.
|
|
||||||
pub fn uncles(&self, hash: &H256) -> Option<Vec<Header>> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).uncles())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of uncle hashes for a given block.
|
|
||||||
/// Returns None if block does not exist.
|
|
||||||
pub fn uncle_hashes(&self, hash: &H256) -> Option<Vec<H256>> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).uncle_hashes())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the familial details concerning a block.
|
|
||||||
pub fn block_details(&self, hash: &H256) -> Option<BlockDetails> {
|
|
||||||
self.query_extras(hash, &self.block_details)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the hash of given block's number.
|
|
||||||
pub fn block_hash(&self, index: BlockNumber) -> Option<H256> {
|
|
||||||
self.query_extras(&index, &self.block_hashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get best block hash.
|
/// Get best block hash.
|
||||||
pub fn best_block_hash(&self) -> H256 {
|
pub fn best_block_hash(&self) -> H256 {
|
||||||
self.best_block.read().unwrap().hash.clone()
|
self.best_block.read().unwrap().hash.clone()
|
||||||
@ -433,40 +474,11 @@ impl BlockChain {
|
|||||||
self.best_block.read().unwrap().total_difficulty
|
self.best_block.read().unwrap().total_difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the number of given block's hash.
|
|
||||||
pub fn block_number(&self, hash: &H256) -> Option<BlockNumber> {
|
|
||||||
self.block(hash).map(|bytes| BlockView::new(&bytes).header_view().number())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the transactions' log blooms of a block.
|
/// Get the transactions' log blooms of a block.
|
||||||
pub fn log_blooms(&self, hash: &H256) -> Option<BlockLogBlooms> {
|
pub fn log_blooms(&self, hash: &H256) -> Option<BlockLogBlooms> {
|
||||||
self.query_extras(hash, &self.block_logs)
|
self.query_extras(hash, &self.block_logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get raw block data
|
|
||||||
pub fn block(&self, hash: &H256) -> Option<Bytes> {
|
|
||||||
{
|
|
||||||
let read = self.blocks.read().unwrap();
|
|
||||||
match read.get(hash) {
|
|
||||||
Some(v) => return Some(v.clone()),
|
|
||||||
None => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let opt = self.blocks_db.get(hash)
|
|
||||||
.expect("Low level database error. Some issue with disk?");
|
|
||||||
|
|
||||||
match opt {
|
|
||||||
Some(b) => {
|
|
||||||
let bytes: Bytes = b.to_vec();
|
|
||||||
let mut write = self.blocks.write().unwrap();
|
|
||||||
write.insert(hash.clone(), bytes.clone());
|
|
||||||
Some(bytes)
|
|
||||||
},
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_extras<K, T>(&self, hash: &K, cache: &RwLock<HashMap<K, T>>) -> Option<T> where
|
fn query_extras<K, T>(&self, hash: &K, cache: &RwLock<HashMap<K, T>>) -> Option<T> where
|
||||||
T: Clone + Decodable + ExtrasIndexable,
|
T: Clone + Decodable + ExtrasIndexable,
|
||||||
K: ExtrasSliceConvertable + Eq + Hash + Clone {
|
K: ExtrasSliceConvertable + Eq + Hash + Clone {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use util::*;
|
use util::*;
|
||||||
use blockchain::BlockChain;
|
use blockchain::{BlockChain, BlockProvider};
|
||||||
use views::BlockView;
|
use views::BlockView;
|
||||||
use error::*;
|
use error::*;
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
|
@ -3,20 +3,20 @@
|
|||||||
use util::*;
|
use util::*;
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Mismatch<T: fmt::Debug> {
|
pub struct Mismatch<T: fmt::Debug> {
|
||||||
pub expected: T,
|
pub expected: T,
|
||||||
pub found: T,
|
pub found: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct OutOfBounds<T: fmt::Debug> {
|
pub struct OutOfBounds<T: fmt::Debug> {
|
||||||
pub min: T,
|
pub min: T,
|
||||||
pub max: T,
|
pub max: T,
|
||||||
pub found: T,
|
pub found: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum BlockError {
|
pub enum BlockError {
|
||||||
TooManyUncles(OutOfBounds<usize>),
|
TooManyUncles(OutOfBounds<usize>),
|
||||||
UncleWrongGeneration,
|
UncleWrongGeneration,
|
||||||
|
@ -10,7 +10,7 @@ pub type BlockNumber = u64;
|
|||||||
/// which is non-specific.
|
/// which is non-specific.
|
||||||
///
|
///
|
||||||
/// Doesn't do all that much on its own.
|
/// Doesn't do all that much on its own.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
// TODO: make all private.
|
// TODO: make all private.
|
||||||
pub parent_hash: H256,
|
pub parent_hash: H256,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use util::*;
|
use util::*;
|
||||||
use blockchain::BlockChain;
|
use blockchain::*;
|
||||||
use views::{BlockView};
|
use views::{BlockView};
|
||||||
use verification::*;
|
use verification::*;
|
||||||
use error::*;
|
use error::*;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
use common::*;
|
use common::*;
|
||||||
use engine::Engine;
|
use engine::Engine;
|
||||||
use blockchain::BlockChain;
|
use blockchain::*;
|
||||||
|
|
||||||
/// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block
|
/// Phase 1 quick block verification. Only does checks that are cheap. Operates on a single block
|
||||||
pub fn verify_block_basic(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
|
pub fn verify_block_basic(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
|
||||||
@ -37,7 +37,7 @@ pub fn verify_block_unordered(bytes: &[u8], engine: &Engine) -> Result<(), Error
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 3 verification. Check block information against parent and uncles.
|
/// Phase 3 verification. Check block information against parent and uncles.
|
||||||
pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Result<(), Error> {
|
pub fn verify_block_final<BC>(bytes: &[u8], engine: &Engine, bc: &BC) -> Result<(), Error> where BC: BlockProvider {
|
||||||
let block = BlockView::new(bytes);
|
let block = BlockView::new(bytes);
|
||||||
let header = block.header();
|
let header = block.header();
|
||||||
let parent = try!(bc.block_header(&header.parent_hash).ok_or::<Error>(From::from(BlockError::UnknownParent(header.parent_hash.clone()))));
|
let parent = try!(bc.block_header(&header.parent_hash).ok_or::<Error>(From::from(BlockError::UnknownParent(header.parent_hash.clone()))));
|
||||||
@ -67,9 +67,8 @@ pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
for uncle in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::<Header>()) {
|
for uncle in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::<Header>()) {
|
||||||
let uncle_parent = try!(bc.block_header(&uncle.parent_hash).ok_or::<Error>(From::from(BlockError::UnknownUncleParent(uncle.parent_hash.clone()))));
|
if excluded.contains(&uncle.hash()) {
|
||||||
if excluded.contains(&uncle_parent.hash()) {
|
return Err(From::from(BlockError::UncleInChain(uncle.hash())))
|
||||||
return Err(From::from(BlockError::UncleInChain(uncle_parent.hash())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_currentBlock.number() - uncle.number() m_cB.n - uP.n()
|
// m_currentBlock.number() - uncle.number() m_cB.n - uP.n()
|
||||||
@ -99,13 +98,20 @@ pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Res
|
|||||||
// cB.p^7 -------------/
|
// cB.p^7 -------------/
|
||||||
// cB.p^8
|
// cB.p^8
|
||||||
let mut expected_uncle_parent = header.parent_hash.clone();
|
let mut expected_uncle_parent = header.parent_hash.clone();
|
||||||
|
let uncle_parent = try!(bc.block_header(&uncle.parent_hash).ok_or::<Error>(From::from(BlockError::UnknownUncleParent(uncle.parent_hash.clone()))));
|
||||||
for _ in 0..depth {
|
for _ in 0..depth {
|
||||||
expected_uncle_parent = bc.block_details(&expected_uncle_parent).unwrap().parent;
|
match bc.block_details(&expected_uncle_parent) {
|
||||||
|
Some(details) => {
|
||||||
|
expected_uncle_parent = details.parent;
|
||||||
|
},
|
||||||
|
None => break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if expected_uncle_parent != uncle_parent.hash() {
|
if expected_uncle_parent != uncle_parent.hash() {
|
||||||
return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash())));
|
return Err(From::from(BlockError::UncleParentNotInChain(uncle_parent.hash())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try!(verify_parent(&uncle, &uncle_parent));
|
||||||
try!(engine.verify_block_final(&uncle, &uncle_parent, Some(bytes)));
|
try!(engine.verify_block_final(&uncle, &uncle_parent, Some(bytes)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +120,7 @@ pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Res
|
|||||||
|
|
||||||
/// Check basic header parameters.
|
/// Check basic header parameters.
|
||||||
fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> {
|
fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> {
|
||||||
if header.number > From::from(BlockNumber::max_value()) {
|
if header.number >= From::from(BlockNumber::max_value()) {
|
||||||
return Err(From::from(BlockError::InvalidNumber(OutOfBounds { max: From::from(BlockNumber::max_value()), min: 0, found: header.number })))
|
return Err(From::from(BlockError::InvalidNumber(OutOfBounds { max: From::from(BlockNumber::max_value()), min: 0, found: header.number })))
|
||||||
}
|
}
|
||||||
if header.gas_used > header.gas_limit {
|
if header.gas_used > header.gas_limit {
|
||||||
@ -122,11 +128,11 @@ fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
|
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
|
||||||
if header.gas_limit < min_gas_limit {
|
if header.gas_limit < min_gas_limit {
|
||||||
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: min_gas_limit, max: From::from(0), found: header.gas_limit })));
|
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: min_gas_limit, max: BAD_U256, found: header.gas_limit })));
|
||||||
}
|
}
|
||||||
let maximum_extra_data_size = engine.maximum_extra_data_size();
|
let maximum_extra_data_size = engine.maximum_extra_data_size();
|
||||||
if header.number != 0 && header.extra_data.len() > maximum_extra_data_size {
|
if header.number != 0 && header.extra_data.len() > maximum_extra_data_size {
|
||||||
return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: 0, max: maximum_extra_data_size, found: header.extra_data.len() })));
|
return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { min: 0, max: maximum_extra_data_size, found: header.extra_data.len() })));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -160,3 +166,227 @@ fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use util::*;
|
||||||
|
use header::*;
|
||||||
|
use verification::*;
|
||||||
|
use extras::*;
|
||||||
|
use error::*;
|
||||||
|
use error::BlockError::*;
|
||||||
|
use views::*;
|
||||||
|
use blockchain::*;
|
||||||
|
use ethereum;
|
||||||
|
|
||||||
|
fn create_test_block(header: &Header) -> Bytes {
|
||||||
|
let mut rlp = RlpStream::new_list(3);
|
||||||
|
rlp.append(header);
|
||||||
|
rlp.append_raw(&rlp::EMPTY_LIST_RLP, 1);
|
||||||
|
rlp.append_raw(&rlp::EMPTY_LIST_RLP, 1);
|
||||||
|
rlp.out()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_test_block_with_data(header: &Header, transactions: &[Bytes], uncles: &[Header]) -> Bytes {
|
||||||
|
let mut rlp = RlpStream::new_list(3);
|
||||||
|
rlp.append(header);
|
||||||
|
rlp.append_list(transactions.len());
|
||||||
|
for t in transactions {
|
||||||
|
rlp.append_raw(t, 1);
|
||||||
|
}
|
||||||
|
rlp.append_list(uncles.len());
|
||||||
|
for h in uncles {
|
||||||
|
rlp.append(h);
|
||||||
|
}
|
||||||
|
rlp.out()
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestBlockChain {
|
||||||
|
pub fn new() -> TestBlockChain {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get raw block data
|
||||||
|
fn block(&self, hash: &H256) -> Option<Bytes> {
|
||||||
|
self.blocks.get(hash).map(|b| b.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
parent: header.parent_hash,
|
||||||
|
children: Vec::new(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the hash of given block's number.
|
||||||
|
fn block_hash(&self, index: BlockNumber) -> Option<H256> {
|
||||||
|
self.numbers.get(&index).map(|h| h.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_block() {
|
||||||
|
// Test against morden
|
||||||
|
let mut good = Header::new();
|
||||||
|
let spec = ethereum::new_morden();
|
||||||
|
let engine = spec.to_engine().unwrap();
|
||||||
|
|
||||||
|
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
|
||||||
|
let min_difficulty = decode(engine.spec().engine_params.get("minimumDifficulty").unwrap());
|
||||||
|
good.gas_limit = min_gas_limit;
|
||||||
|
good.difficulty = min_difficulty;
|
||||||
|
good.timestamp = 40;
|
||||||
|
good.number = 10;
|
||||||
|
|
||||||
|
let good_transactions = vec![ vec![ 1u8 ], vec![ 2u8 ] ]; // TODO: proper transactions
|
||||||
|
|
||||||
|
let diff_inc = U256::from(0x40);
|
||||||
|
|
||||||
|
let mut parent6 = good.clone();
|
||||||
|
parent6.number = 6;
|
||||||
|
let mut parent7 = good.clone();
|
||||||
|
parent7.number = 7;
|
||||||
|
parent7.parent_hash = parent6.hash();
|
||||||
|
parent7.difficulty = parent6.difficulty + diff_inc;
|
||||||
|
parent7.timestamp = parent6.timestamp + 10;
|
||||||
|
let mut parent8 = good.clone();
|
||||||
|
parent8.number = 8;
|
||||||
|
parent8.parent_hash = parent7.hash();
|
||||||
|
parent8.difficulty = parent7.difficulty + diff_inc;
|
||||||
|
parent8.timestamp = parent7.timestamp + 10;
|
||||||
|
|
||||||
|
let mut good_uncle1 = good.clone();
|
||||||
|
good_uncle1.number = 9;
|
||||||
|
good_uncle1.parent_hash = parent8.hash();
|
||||||
|
good_uncle1.difficulty = parent8.difficulty + diff_inc;
|
||||||
|
good_uncle1.timestamp = parent8.timestamp + 10;
|
||||||
|
good_uncle1.extra_data.push(1u8);
|
||||||
|
|
||||||
|
let mut good_uncle2 = good.clone();
|
||||||
|
good_uncle2.number = 8;
|
||||||
|
good_uncle2.parent_hash = parent7.hash();
|
||||||
|
good_uncle2.difficulty = parent7.difficulty + diff_inc;
|
||||||
|
good_uncle2.timestamp = parent7.timestamp + 10;
|
||||||
|
good_uncle2.extra_data.push(2u8);
|
||||||
|
|
||||||
|
let good_uncles = vec![ good_uncle1, good_uncle2 ];
|
||||||
|
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.clone());
|
||||||
|
|
||||||
|
let mut parent = good.clone();
|
||||||
|
parent.number = 9;
|
||||||
|
parent.timestamp = parent8.timestamp + 10;
|
||||||
|
parent.parent_hash = parent8.hash();
|
||||||
|
parent.difficulty = parent8.difficulty + diff_inc;
|
||||||
|
|
||||||
|
good.parent_hash = parent.hash();
|
||||||
|
good.difficulty = parent.difficulty + diff_inc;
|
||||||
|
good.timestamp = parent.timestamp + 10;
|
||||||
|
|
||||||
|
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(verify_block_basic(&create_test_block(&good), engine.deref()));
|
||||||
|
|
||||||
|
let mut header = good.clone();
|
||||||
|
header.transactions_root = good_transactions_root.clone();
|
||||||
|
header.uncles_hash = good_uncles_hash.clone();
|
||||||
|
check_ok(verify_block_basic(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref()));
|
||||||
|
|
||||||
|
header.gas_limit = min_gas_limit - From::from(1);
|
||||||
|
check_fail(verify_block_basic(&create_test_block(&header), engine.deref()),
|
||||||
|
InvalidGasLimit(OutOfBounds { min: min_gas_limit, max: BAD_U256, found: header.gas_limit }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.number = BlockNumber::max_value();
|
||||||
|
check_fail(verify_block_basic(&create_test_block(&header), engine.deref()),
|
||||||
|
InvalidNumber(OutOfBounds { max: BlockNumber::max_value(), min: 0, found: header.number }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.gas_used = header.gas_limit + From::from(1);
|
||||||
|
check_fail(verify_block_basic(&create_test_block(&header), engine.deref()),
|
||||||
|
TooMuchGasUsed(OutOfBounds { max: header.gas_limit, min: From::from(0), found: header.gas_used }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.extra_data.resize(engine.maximum_extra_data_size() + 1, 0u8);
|
||||||
|
check_fail(verify_block_basic(&create_test_block(&header), engine.deref()),
|
||||||
|
ExtraDataOutOfBounds(OutOfBounds { max: engine.maximum_extra_data_size(), min: 0, found: header.extra_data.len() }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.extra_data.resize(engine.maximum_extra_data_size() + 1, 0u8);
|
||||||
|
check_fail(verify_block_basic(&create_test_block(&header), engine.deref()),
|
||||||
|
ExtraDataOutOfBounds(OutOfBounds { max: engine.maximum_extra_data_size(), min: 0, found: header.extra_data.len() }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.uncles_hash = good_uncles_hash.clone();
|
||||||
|
check_fail(verify_block_basic(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref()),
|
||||||
|
InvalidTransactionsRoot(Mismatch { expected: good_transactions_root.clone(), found: header.transactions_root }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.transactions_root = good_transactions_root.clone();
|
||||||
|
check_fail(verify_block_basic(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref()),
|
||||||
|
InvalidUnclesHash(Mismatch { expected: good_uncles_hash.clone(), found: header.uncles_hash }));
|
||||||
|
|
||||||
|
check_ok(verify_block_final(&create_test_block(&good), engine.deref(), &bc));
|
||||||
|
check_ok(verify_block_final(&create_test_block_with_data(&good, &good_transactions, &good_uncles), engine.deref(), &bc));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.parent_hash = H256::random();
|
||||||
|
check_fail(verify_block_final(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref(), &bc),
|
||||||
|
UnknownParent(header.parent_hash));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.timestamp = 10;
|
||||||
|
check_fail(verify_block_final(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref(), &bc),
|
||||||
|
InvalidTimestamp(OutOfBounds { max: u64::max_value(), min: parent.timestamp + 1, found: header.timestamp }));
|
||||||
|
|
||||||
|
header = good.clone();
|
||||||
|
header.number = 9;
|
||||||
|
check_fail(verify_block_final(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref(), &bc),
|
||||||
|
InvalidNumber(OutOfBounds { max: BlockNumber::max_value(), min: parent.number + 1, found: header.number }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user