Merge branch 'master' into evm
This commit is contained in:
commit
d7176faddc
@ -16,7 +16,7 @@ rocksdb = "0.2"
|
||||
heapsize = "0.2.0"
|
||||
rust-crypto = "0.2.34"
|
||||
time = "0.1"
|
||||
|
||||
#interpolate_idents = { git = "https://github.com/SkylerLipthay/interpolate_idents" }
|
||||
evmjit = { path = "rust-evmjit", optional = true }
|
||||
|
||||
[features]
|
||||
|
@ -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.
|
||||
///
|
||||
/// **Does not do input data verification.**
|
||||
@ -67,6 +116,48 @@ pub struct BlockChain {
|
||||
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 {
|
||||
/// Create new instance of blockchain from given Genesis
|
||||
///
|
||||
@ -363,61 +454,11 @@ impl BlockChain {
|
||||
(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.
|
||||
pub fn is_known_transaction(&self, hash: &H256) -> bool {
|
||||
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.
|
||||
pub fn best_block_hash(&self) -> H256 {
|
||||
self.best_block.read().unwrap().hash.clone()
|
||||
@ -433,40 +474,11 @@ impl BlockChain {
|
||||
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.
|
||||
pub fn log_blooms(&self, hash: &H256) -> Option<BlockLogBlooms> {
|
||||
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
|
||||
T: Clone + Decodable + ExtrasIndexable,
|
||||
K: ExtrasSliceConvertable + Eq + Hash + Clone {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use util::*;
|
||||
use blockchain::BlockChain;
|
||||
use blockchain::{BlockChain, BlockProvider};
|
||||
use views::BlockView;
|
||||
use error::*;
|
||||
use header::BlockNumber;
|
||||
|
@ -3,13 +3,13 @@
|
||||
use util::*;
|
||||
use header::BlockNumber;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Mismatch<T: fmt::Debug> {
|
||||
pub expected: T,
|
||||
pub found: T,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct OutOfBounds<T: fmt::Debug> {
|
||||
pub min: Option<T>,
|
||||
pub max: Option<T>,
|
||||
@ -28,7 +28,7 @@ pub enum ExecutionError {
|
||||
InvalidNonce { expected: U256, is: U256 },
|
||||
/// Returned when cost of transaction (value + gas_price * gas) exceeds
|
||||
/// current sender balance.
|
||||
NotEnoughCash { required: U256, is: U256 },
|
||||
NotEnoughCash { required: U512, is: U512 },
|
||||
/// Returned when internal evm error occurs.
|
||||
Internal
|
||||
}
|
||||
@ -38,7 +38,7 @@ pub enum TransactionError {
|
||||
InvalidGasLimit(OutOfBounds<U256>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BlockError {
|
||||
TooManyUncles(OutOfBounds<usize>),
|
||||
UncleWrongGeneration,
|
||||
|
@ -51,14 +51,6 @@ impl Engine for Ethash {
|
||||
if header.difficulty < min_difficulty {
|
||||
return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: min_difficulty, found: header.difficulty })))
|
||||
}
|
||||
let min_gas_limit = decode(self.spec().engine_params.get("minGasLimit").unwrap());
|
||||
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 })));
|
||||
}
|
||||
let maximum_extra_data_size = self.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() })));
|
||||
}
|
||||
// TODO: Verify seal (quick)
|
||||
Ok(())
|
||||
}
|
||||
|
401
src/executive.rs
401
src/executive.rs
@ -21,6 +21,8 @@ struct Substate {
|
||||
logs: Vec<LogEntry>,
|
||||
/// Refund counter of SSTORE nonzero->zero.
|
||||
refunds_count: U256,
|
||||
/// Created contracts.
|
||||
contracts_created: Vec<Address>
|
||||
}
|
||||
|
||||
impl Substate {
|
||||
@ -30,11 +32,13 @@ impl Substate {
|
||||
suicides: HashSet::new(),
|
||||
logs: vec![],
|
||||
refunds_count: U256::zero(),
|
||||
contracts_created: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction execution receipt.
|
||||
#[derive(Debug)]
|
||||
pub struct Executed {
|
||||
/// Gas paid up front for execution of transaction.
|
||||
pub gas: U256,
|
||||
@ -51,9 +55,15 @@ pub struct Executed {
|
||||
pub cumulative_gas_used: U256,
|
||||
/// Vector of logs generated by transaction.
|
||||
pub logs: Vec<LogEntry>,
|
||||
|
||||
/// Execution ended running out of gas.
|
||||
pub out_of_gas: bool,
|
||||
/// Addresses of contracts created during execution of transaction.
|
||||
/// Ordered from earliest creation.
|
||||
///
|
||||
/// eg. sender creates contract A and A in constructor creates contract B
|
||||
///
|
||||
/// B creation ends first, and it will be the first element of the vector.
|
||||
pub contracts_created: Vec<Address>
|
||||
}
|
||||
|
||||
/// Transaction execution result.
|
||||
@ -64,7 +74,7 @@ pub struct Executive<'a> {
|
||||
state: &'a mut State,
|
||||
info: &'a EnvInfo,
|
||||
engine: &'a Engine,
|
||||
depth: usize,
|
||||
depth: usize
|
||||
}
|
||||
|
||||
impl<'a> Executive<'a> {
|
||||
@ -85,14 +95,12 @@ impl<'a> Executive<'a> {
|
||||
state: state,
|
||||
info: info,
|
||||
engine: engine,
|
||||
depth: depth,
|
||||
depth: depth
|
||||
}
|
||||
}
|
||||
|
||||
/// This funtion should be used to execute transaction.
|
||||
pub fn transact(&mut self, t: &Transaction) -> Result<Executed, Error> {
|
||||
// TODO: validate transaction signature ?/ sender
|
||||
|
||||
let sender = try!(t.sender());
|
||||
let nonce = self.state.nonce(&sender);
|
||||
|
||||
@ -112,18 +120,19 @@ impl<'a> Executive<'a> {
|
||||
|
||||
// TODO: we might need bigints here, or at least check overflows.
|
||||
let balance = self.state.balance(&sender);
|
||||
let gas_cost = t.gas * t.gas_price;
|
||||
let total_cost = t.value + gas_cost;
|
||||
let gas_cost = U512::from(t.gas) * U512::from(t.gas_price);
|
||||
let total_cost = U512::from(t.value) + gas_cost;
|
||||
|
||||
// avoid unaffordable transactions
|
||||
if balance < total_cost {
|
||||
return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, is: balance }));
|
||||
if U512::from(balance) < total_cost {
|
||||
return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, is: U512::from(balance) }));
|
||||
}
|
||||
|
||||
// NOTE: there can be no invalid transactions from this point.
|
||||
self.state.inc_nonce(&sender);
|
||||
let mut substate = Substate::new();
|
||||
self.state.sub_balance(&sender, &U256::from(gas_cost));
|
||||
|
||||
let mut substate = Substate::new();
|
||||
let backup = self.state.clone();
|
||||
|
||||
let res = match t.action() {
|
||||
@ -216,6 +225,7 @@ impl<'a> Executive<'a> {
|
||||
cumulative_gas_used: self.info.gas_used + t.gas,
|
||||
logs: vec![],
|
||||
out_of_gas: true,
|
||||
contracts_created: vec![]
|
||||
})
|
||||
},
|
||||
Ok(gas_left) => {
|
||||
@ -232,9 +242,10 @@ impl<'a> Executive<'a> {
|
||||
self.state.add_balance(&t.sender().unwrap(), &refund_value);
|
||||
|
||||
// fees earned by author
|
||||
let fees = (t.gas - refund) * t.gas_price;
|
||||
let fees = t.gas - refund;
|
||||
let fees_value = fees * t.gas_price;
|
||||
let author = &self.info.author;
|
||||
self.state.add_balance(author, &fees);
|
||||
self.state.add_balance(author, &fees_value);
|
||||
|
||||
// perform suicides
|
||||
for address in substate.suicides.iter() {
|
||||
@ -249,6 +260,7 @@ impl<'a> Executive<'a> {
|
||||
cumulative_gas_used: self.info.gas_used + gas_used,
|
||||
logs: substate.logs,
|
||||
out_of_gas: false,
|
||||
contracts_created: substate.contracts_created
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -332,7 +344,7 @@ impl<'a> Ext for Externalities<'a> {
|
||||
|
||||
fn create(&mut self, gas: u64, value: &U256, code: &[u8]) -> Result<(u64, Option<Address>), evm::Error> {
|
||||
// if balance is insufficient or we are to deep, return
|
||||
if self.state.balance(&self.params.address) < *value && self.depth >= 1024 {
|
||||
if self.state.balance(&self.params.address) < *value || self.depth >= self.schedule.stack_limit {
|
||||
return Ok((gas, None));
|
||||
}
|
||||
|
||||
@ -361,7 +373,7 @@ impl<'a> Ext for Externalities<'a> {
|
||||
let mut call_gas = call_gas;
|
||||
|
||||
let is_call = receive_address == code_address;
|
||||
if is_call && self.state.code(&code_address).is_none() {
|
||||
if is_call && !self.state.exists(&code_address) {
|
||||
gas_cost = gas_cost + self.schedule.call_new_account_gas as u64;
|
||||
}
|
||||
|
||||
@ -377,9 +389,8 @@ impl<'a> Ext for Externalities<'a> {
|
||||
|
||||
let gas = gas - gas_cost;
|
||||
|
||||
//println!("depth: {:?}", self.depth);
|
||||
// if balance is insufficient or we are to deep, return
|
||||
if self.state.balance(&self.params.address) < *value && self.depth >= 1024 {
|
||||
if self.state.balance(&self.params.address) < *value || self.depth >= self.schedule.stack_limit {
|
||||
return Ok(gas + call_gas)
|
||||
}
|
||||
|
||||
@ -395,7 +406,9 @@ impl<'a> Ext for Externalities<'a> {
|
||||
};
|
||||
|
||||
let mut ex = Executive::from_parent(self.state, self.info, self.engine, self.depth);
|
||||
ex.call(¶ms, self.substate, output).map(|gas_left| gas + gas_left.low_u64())
|
||||
ex.call(¶ms, self.substate, output).map(|gas_left| {
|
||||
gas + gas_left.low_u64()
|
||||
})
|
||||
}
|
||||
|
||||
fn extcode(&self, address: &Address) -> Vec<u8> {
|
||||
@ -403,6 +416,7 @@ impl<'a> Ext for Externalities<'a> {
|
||||
}
|
||||
|
||||
fn ret(&mut self, gas: u64, data: &[u8]) -> Result<u64, evm::Error> {
|
||||
println!("ret");
|
||||
match &mut self.output {
|
||||
&mut OutputPolicy::Return(ref mut slice) => unsafe {
|
||||
let len = cmp::min(slice.len(), data.len());
|
||||
@ -412,7 +426,10 @@ impl<'a> Ext for Externalities<'a> {
|
||||
&mut OutputPolicy::InitContract => {
|
||||
let return_cost = data.len() as u64 * self.schedule.create_data_gas as u64;
|
||||
if return_cost > gas {
|
||||
return Err(evm::Error::OutOfGas);
|
||||
return match self.schedule.exceptional_failed_code_deposit {
|
||||
true => Err(evm::Error::OutOfGas),
|
||||
false => Ok(gas)
|
||||
}
|
||||
}
|
||||
let mut code = vec![];
|
||||
code.reserve(data.len());
|
||||
@ -422,6 +439,7 @@ impl<'a> Ext for Externalities<'a> {
|
||||
}
|
||||
let address = &self.params.address;
|
||||
self.state.init_code(address, code);
|
||||
self.substate.contracts_created.push(address.clone());
|
||||
Ok(gas - return_cost)
|
||||
}
|
||||
}
|
||||
@ -452,9 +470,35 @@ mod tests {
|
||||
use common::*;
|
||||
use state::*;
|
||||
use ethereum;
|
||||
use null_engine::*;
|
||||
use engine::*;
|
||||
use spec::*;
|
||||
use evm::Schedule;
|
||||
use super::Substate;
|
||||
|
||||
struct TestEngine {
|
||||
spec: Spec,
|
||||
stack_limit: usize
|
||||
}
|
||||
|
||||
impl TestEngine {
|
||||
fn new(stack_limit: usize) -> TestEngine {
|
||||
TestEngine {
|
||||
spec: ethereum::new_frontier(),
|
||||
stack_limit: stack_limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine for TestEngine {
|
||||
fn name(&self) -> &str { "TestEngine" }
|
||||
fn spec(&self) -> &Spec { &self.spec }
|
||||
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
||||
let mut schedule = Schedule::new_frontier();
|
||||
schedule.stack_limit = self.stack_limit;
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contract_address() {
|
||||
let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
|
||||
@ -464,35 +508,114 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
// TODO: replace params with transactions!
|
||||
fn test_executive() {
|
||||
fn test_sender_balance() {
|
||||
let sender = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
|
||||
let address = contract_address(&sender, &U256::zero());
|
||||
let mut params = ActionParams::new();
|
||||
params.address = address.clone();
|
||||
params.sender = sender.clone();
|
||||
params.gas = U256::from(0x174876e800u64);
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = "3331600055".from_hex().unwrap();
|
||||
params.value = U256::from(0x7);
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(0x100u64));
|
||||
let info = EnvInfo::new();
|
||||
let engine = NullEngine::new_boxed(ethereum::new_frontier());
|
||||
let engine = TestEngine::new(0);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
{
|
||||
let mut ex = Executive::new(&mut state, &info, engine.deref());
|
||||
let _res = ex.create(¶ms, &mut substate);
|
||||
}
|
||||
let gas_left = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.create(¶ms, &mut substate).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(79_975));
|
||||
assert_eq!(state.storage_at(&address, &H256::new()), H256::from(&U256::from(0xf9u64)));
|
||||
assert_eq!(state.balance(&sender), U256::from(0xf9));
|
||||
assert_eq!(state.balance(&address), U256::from(0x7));
|
||||
// 0 cause contract hasn't returned
|
||||
assert_eq!(substate.contracts_created.len(), 0);
|
||||
|
||||
// TODO: just test state root.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_contract() {
|
||||
// code:
|
||||
//
|
||||
// 7c 601080600c6000396000f3006000355415600957005b60203560003555 - push 29 bytes?
|
||||
// 60 00 - push 0
|
||||
// 52
|
||||
// 60 1d - push 29
|
||||
// 60 03 - push 3
|
||||
// 60 17 - push 17
|
||||
// f0 - create
|
||||
// 60 00 - push 0
|
||||
// 55 sstore
|
||||
//
|
||||
// other code:
|
||||
//
|
||||
// 60 10 - push 16
|
||||
// 80 - duplicate first stack item
|
||||
// 60 0c - push 12
|
||||
// 60 00 - push 0
|
||||
// 39 - copy current code to memory
|
||||
// 60 00 - push 0
|
||||
// f3 - return
|
||||
|
||||
let code = "7c601080600c6000396000f3006000355415600957005b60203560003555600052601d60036017f0600055".from_hex().unwrap();
|
||||
|
||||
let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
|
||||
let address = contract_address(&sender, &U256::zero());
|
||||
// TODO: add tests for 'callcreate'
|
||||
//let next_address = contract_address(&address, &U256::zero());
|
||||
let mut params = ActionParams::new();
|
||||
params.address = address.clone();
|
||||
params.sender = sender.clone();
|
||||
params.origin = sender.clone();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = code.clone();
|
||||
params.value = U256::from(100);
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(100));
|
||||
let info = EnvInfo::new();
|
||||
let engine = TestEngine::new(0);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.create(¶ms, &mut substate).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(47_976));
|
||||
assert_eq!(substate.contracts_created.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_contract_without_stack_limit() {
|
||||
// code:
|
||||
//
|
||||
// 7c 601080600c6000396000f3006000355415600957005b60203560003555 - push 29 bytes?
|
||||
// 60 00 - push 0
|
||||
// 52
|
||||
// 60 1d - push 29
|
||||
// 60 03 - push 3
|
||||
// 60 17 - push 17
|
||||
// f0 - create
|
||||
// 60 00 - push 0
|
||||
// 55 sstore
|
||||
//
|
||||
// other code:
|
||||
//
|
||||
// 60 10 - push 16
|
||||
// 80 - duplicate first stack item
|
||||
// 60 0c - push 12
|
||||
// 60 00 - push 0
|
||||
// 39 - copy current code to memory
|
||||
// 60 00 - push 0
|
||||
// f3 - return
|
||||
|
||||
let code = "7c601080600c6000396000f3006000355415600957005b60203560003555600052601d60036017f0600055".from_hex().unwrap();
|
||||
|
||||
let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
|
||||
let address = contract_address(&sender, &U256::zero());
|
||||
let next_address = contract_address(&address, &U256::zero());
|
||||
@ -500,23 +623,80 @@ mod tests {
|
||||
params.address = address.clone();
|
||||
params.sender = sender.clone();
|
||||
params.origin = sender.clone();
|
||||
params.gas = U256::from(0x174876e800u64);
|
||||
params.code = "7c601080600c6000396000f3006000355415600957005b60203560003555600052601d60036000f0600055".from_hex().unwrap();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = code.clone();
|
||||
params.value = U256::from(100);
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(0x100u64));
|
||||
state.add_balance(&sender, &U256::from(100));
|
||||
let info = EnvInfo::new();
|
||||
let engine = NullEngine::new_boxed(ethereum::new_frontier());
|
||||
let engine = TestEngine::new(1024);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
{
|
||||
let mut ex = Executive::new(&mut state, &info, engine.deref());
|
||||
let _res = ex.create(¶ms, &mut substate);
|
||||
println!("res: {:?}", _res);
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.create(¶ms, &mut substate).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(state.storage_at(&address, &H256::new()), H256::from(next_address.clone()));
|
||||
assert_eq!(state.code(&next_address).unwrap(), "6000355415600957005b602035600035".from_hex().unwrap());
|
||||
//assert!(false);
|
||||
assert_eq!(substate.contracts_created.len(), 1);
|
||||
assert_eq!(substate.contracts_created[0], next_address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aba_calls() {
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 18 - push 18
|
||||
// 73 945304eb96065b2a98b57a48a06ae28d285a71b5 - push this address
|
||||
// 61 03e8 - push 1000
|
||||
// f1 - message call
|
||||
// 58 - get PC
|
||||
// 55 - sstore
|
||||
|
||||
let code_a = "6000600060006000601873945304eb96065b2a98b57a48a06ae28d285a71b56103e8f15855".from_hex().unwrap();
|
||||
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 00 - push 0
|
||||
// 60 17 - push 17
|
||||
// 73 0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6 - push this address
|
||||
// 61 0x01f4 - push 500
|
||||
// f1 - message call
|
||||
// 60 01 - push 1
|
||||
// 01 - add
|
||||
// 58 - get PC
|
||||
// 55 - sstore
|
||||
let code_b = "60006000600060006017730f572e5295c57f15886f9b263e2f6d2d6c7b5ec66101f4f16001015855".from_hex().unwrap();
|
||||
|
||||
let address_a = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
|
||||
let address_b = Address::from_str("945304eb96065b2a98b57a48a06ae28d285a71b5" ).unwrap();
|
||||
let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
|
||||
|
||||
let mut params = ActionParams::new();
|
||||
params.address = address_a.clone();
|
||||
params.sender = sender.clone();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = code_a.clone();
|
||||
params.value = U256::from(100_000);
|
||||
|
||||
let mut state = State::new_temp();
|
||||
state.init_code(&address_a, code_a.clone());
|
||||
state.init_code(&address_b, code_b.clone());
|
||||
state.add_balance(&sender, &U256::from(100_000));
|
||||
|
||||
let info = EnvInfo::new();
|
||||
let engine = TestEngine::new(0);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.call(¶ms, &mut substate, &mut []).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(73_237));
|
||||
assert_eq!(state.storage_at(&address_a, &H256::from(&U256::from(0x23))), H256::from(&U256::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -538,28 +718,155 @@ mod tests {
|
||||
// 03 - sub
|
||||
// f1 - message call (self in this case)
|
||||
// 60 01 - push 1
|
||||
// 55 - store
|
||||
// 55 - sstore
|
||||
let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
|
||||
let code = "600160005401600055600060006000600060003060e05a03f1600155".from_hex().unwrap();
|
||||
let address = contract_address(&sender, &U256::zero());
|
||||
let mut params = ActionParams::new();
|
||||
params.address = address.clone();
|
||||
params.sender = sender.clone();
|
||||
params.origin = sender.clone();
|
||||
params.gas = U256::from(0x590b3);
|
||||
params.gas_price = U256::one();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = code.clone();
|
||||
println!("init gas: {:?}", params.gas.low_u64());
|
||||
let mut state = State::new_temp();
|
||||
state.init_code(&address, code.clone());
|
||||
let info = EnvInfo::new();
|
||||
let engine = NullEngine::new_boxed(ethereum::new_frontier());
|
||||
let engine = TestEngine::new(0);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
{
|
||||
let mut ex = Executive::new(&mut state, &info, engine.deref());
|
||||
let _res = ex.call(¶ms, &mut substate, &mut []);
|
||||
println!("res: {:?}", _res);
|
||||
let gas_left = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.call(¶ms, &mut substate, &mut []).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(59_870));
|
||||
assert_eq!(state.storage_at(&address, &H256::from(&U256::zero())), H256::from(&U256::from(1)));
|
||||
assert_eq!(state.storage_at(&address, &H256::from(&U256::one())), H256::from(&U256::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transact_simple() {
|
||||
let mut t = Transaction::new_create(U256::from(17), "3331600055".from_hex().unwrap(), U256::from(100_000), U256::zero(), U256::zero());
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
t.sign(&keypair.secret());
|
||||
let sender = t.sender().unwrap();
|
||||
let contract = contract_address(&sender, &U256::zero());
|
||||
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(18));
|
||||
let mut info = EnvInfo::new();
|
||||
info.gas_limit = U256::from(100_000);
|
||||
let engine = TestEngine::new(0);
|
||||
|
||||
let executed = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.transact(&t).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(executed.gas, U256::from(100_000));
|
||||
assert_eq!(executed.gas_used, U256::from(20_025));
|
||||
assert_eq!(executed.refunded, U256::from(79_975));
|
||||
assert_eq!(executed.cumulative_gas_used, U256::from(20_025));
|
||||
assert_eq!(executed.logs.len(), 0);
|
||||
assert_eq!(executed.out_of_gas, false);
|
||||
assert_eq!(executed.contracts_created.len(), 0);
|
||||
assert_eq!(state.balance(&sender), U256::from(1));
|
||||
assert_eq!(state.balance(&contract), U256::from(17));
|
||||
assert_eq!(state.nonce(&sender), U256::from(1));
|
||||
assert_eq!(state.storage_at(&contract, &H256::new()), H256::from(&U256::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transact_invalid_sender() {
|
||||
let t = Transaction::new_create(U256::from(17), "3331600055".from_hex().unwrap(), U256::from(100_000), U256::zero(), U256::zero());
|
||||
|
||||
let mut state = State::new_temp();
|
||||
let mut info = EnvInfo::new();
|
||||
info.gas_limit = U256::from(100_000);
|
||||
let engine = TestEngine::new(0);
|
||||
|
||||
let res = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.transact(&t)
|
||||
};
|
||||
|
||||
match res {
|
||||
Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => (),
|
||||
_ => assert!(false, "Expected invalid signature error.")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transact_invalid_nonce() {
|
||||
let mut t = Transaction::new_create(U256::from(17), "3331600055".from_hex().unwrap(), U256::from(100_000), U256::zero(), U256::one());
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
t.sign(&keypair.secret());
|
||||
let sender = t.sender().unwrap();
|
||||
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(17));
|
||||
let mut info = EnvInfo::new();
|
||||
info.gas_limit = U256::from(100_000);
|
||||
let engine = TestEngine::new(0);
|
||||
|
||||
let res = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.transact(&t)
|
||||
};
|
||||
|
||||
match res {
|
||||
Err(Error::Execution(ExecutionError::InvalidNonce { expected, is }))
|
||||
if expected == U256::zero() && is == U256::one() => (),
|
||||
_ => assert!(false, "Expected invalid nonce error.")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transact_gas_limit_reached() {
|
||||
let mut t = Transaction::new_create(U256::from(17), "3331600055".from_hex().unwrap(), U256::from(80_001), U256::zero(), U256::zero());
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
t.sign(&keypair.secret());
|
||||
let sender = t.sender().unwrap();
|
||||
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(17));
|
||||
let mut info = EnvInfo::new();
|
||||
info.gas_used = U256::from(20_000);
|
||||
info.gas_limit = U256::from(100_000);
|
||||
let engine = TestEngine::new(0);
|
||||
|
||||
let res = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.transact(&t)
|
||||
};
|
||||
|
||||
match res {
|
||||
Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas }))
|
||||
if gas_limit == U256::from(100_000) && gas_used == U256::from(20_000) && gas == U256::from(80_001) => (),
|
||||
_ => assert!(false, "Expected block gas limit error.")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_enough_cash() {
|
||||
let mut t = Transaction::new_create(U256::from(18), "3331600055".from_hex().unwrap(), U256::from(100_000), U256::one(), U256::zero());
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
t.sign(&keypair.secret());
|
||||
let sender = t.sender().unwrap();
|
||||
|
||||
let mut state = State::new_temp();
|
||||
state.add_balance(&sender, &U256::from(100_017));
|
||||
let mut info = EnvInfo::new();
|
||||
info.gas_limit = U256::from(100_000);
|
||||
let engine = TestEngine::new(0);
|
||||
|
||||
let res = {
|
||||
let mut ex = Executive::new(&mut state, &info, &engine);
|
||||
ex.transact(&t)
|
||||
};
|
||||
|
||||
match res {
|
||||
Err(Error::Execution(ExecutionError::NotEnoughCash { required , is }))
|
||||
if required == U512::zero() && is == U512::one() => (),
|
||||
_ => assert!(false, "Expected not enough cash error.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub type BlockNumber = u64;
|
||||
/// which is non-specific.
|
||||
///
|
||||
/// Doesn't do all that much on its own.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
// TODO: make all private.
|
||||
pub parent_hash: H256,
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![feature(cell_extras)]
|
||||
#![feature(augmented_assignments)]
|
||||
//#![feature(plugin)]
|
||||
//#![plugin(interpolate_idents)]
|
||||
//! Ethcore's ethereum implementation
|
||||
//!
|
||||
//! ### Rust version
|
||||
@ -107,6 +109,9 @@ pub mod blockchain;
|
||||
pub mod extras;
|
||||
pub mod evm;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod client;
|
||||
pub mod sync;
|
||||
pub mod block;
|
||||
|
@ -2,6 +2,7 @@ use util::*;
|
||||
use basic_types::LogBloom;
|
||||
|
||||
/// A single log's entry.
|
||||
#[derive(Debug)]
|
||||
pub struct LogEntry {
|
||||
pub address: Address,
|
||||
pub topics: Vec<H256>,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use util::*;
|
||||
use blockchain::BlockChain;
|
||||
use blockchain::*;
|
||||
use views::{BlockView};
|
||||
use verification::*;
|
||||
use error::*;
|
||||
|
12
src/state.rs
12
src/state.rs
@ -76,6 +76,11 @@ impl State {
|
||||
self.cache.borrow_mut().insert(account.clone(), None);
|
||||
}
|
||||
|
||||
/// Determine whether an account exists.
|
||||
pub fn exists(&self, a: &Address) -> bool {
|
||||
self.cache.borrow().get(&a).unwrap_or(&None).is_some() || SecTrieDB::new(&self.db, &self.root).contains(&a)
|
||||
}
|
||||
|
||||
/// Get the balance of account `a`.
|
||||
pub fn balance(&self, a: &Address) -> U256 {
|
||||
self.get(a, false).as_ref().map(|account| account.balance().clone()).unwrap_or(U256::from(0u8))
|
||||
@ -287,9 +292,12 @@ fn get_from_database() {
|
||||
fn remove() {
|
||||
let a = Address::zero();
|
||||
let mut s = State::new_temp();
|
||||
assert_eq!(s.exists(&a), false);
|
||||
s.inc_nonce(&a);
|
||||
assert_eq!(s.exists(&a), true);
|
||||
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||
s.kill_account(&a);
|
||||
assert_eq!(s.exists(&a), false);
|
||||
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||
}
|
||||
|
||||
@ -300,20 +308,24 @@ fn remove_from_database() {
|
||||
let mut s = State::new_temp();
|
||||
s.inc_nonce(&a);
|
||||
s.commit();
|
||||
assert_eq!(s.exists(&a), true);
|
||||
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||
s.drop()
|
||||
};
|
||||
|
||||
let (r, db) = {
|
||||
let mut s = State::from_existing(db, r, U256::from(0u8));
|
||||
assert_eq!(s.exists(&a), true);
|
||||
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||
s.kill_account(&a);
|
||||
s.commit();
|
||||
assert_eq!(s.exists(&a), false);
|
||||
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||
s.drop()
|
||||
};
|
||||
|
||||
let s = State::from_existing(db, r, U256::from(0u8));
|
||||
assert_eq!(s.exists(&a), false);
|
||||
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||
}
|
||||
|
||||
|
4
src/tests/mod.rs
Normal file
4
src/tests/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[macro_use]
|
||||
mod test_common;
|
||||
|
||||
mod transaction;
|
62
src/tests/test_common.rs
Normal file
62
src/tests/test_common.rs
Normal file
@ -0,0 +1,62 @@
|
||||
pub use common::*;
|
||||
|
||||
pub fn clean(s: &str) -> &str {
|
||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
||||
&s[2..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_from_json(json: &Json) -> Bytes {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() % 2 == 1 {
|
||||
FromHex::from_hex(&("0".to_string() + &(clean(s).to_string()))[..]).unwrap_or(vec![])
|
||||
} else {
|
||||
FromHex::from_hex(clean(s)).unwrap_or(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address_from_json(json: &Json) -> Address {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() % 2 == 1 {
|
||||
address_from_hex(&("0".to_string() + &(clean(s).to_string()))[..])
|
||||
} else {
|
||||
address_from_hex(clean(s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u256_from_json(json: &Json) -> U256 {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
||||
// hex
|
||||
U256::from_str(&s[2..]).unwrap()
|
||||
}
|
||||
else {
|
||||
// dec
|
||||
U256::from_dec_str(s).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! declare_test {
|
||||
($id: ident, $name: expr) => {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $id() {
|
||||
assert!(do_json_test(include_bytes!(concat!("../../res/ethereum/tests/", $name, ".json"))).len() == 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! declare_test_ignore {
|
||||
($id: ident, $name: expr) => {
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[allow(non_snake_case)]
|
||||
fn $id() {
|
||||
assert!(do_json_test(include_bytes!(concat!("../../res/ethereum/tests/", $name, ".json"))).len() == 0);
|
||||
}
|
||||
};
|
||||
}
|
75
src/tests/transaction.rs
Normal file
75
src/tests/transaction.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use super::test_common::*;
|
||||
use evm;
|
||||
|
||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||
let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid");
|
||||
let mut failed = Vec::new();
|
||||
let old_schedule = evm::Schedule::new_frontier();
|
||||
let new_schedule = evm::Schedule::new_homestead();
|
||||
for (name, test) in json.as_object().unwrap() {
|
||||
let mut fail = false;
|
||||
let mut fail_unless = |cond: bool| if !cond && fail { failed.push(name.to_string()); fail = true };
|
||||
let schedule = match test.find("blocknumber")
|
||||
.and_then(|j| j.as_string())
|
||||
.and_then(|s| BlockNumber::from_str(s).ok())
|
||||
.unwrap_or(0) { x if x < 900000 => &old_schedule, _ => &new_schedule };
|
||||
let rlp = bytes_from_json(&test["rlp"]);
|
||||
let res = UntrustedRlp::new(&rlp).as_val().map_err(|e| From::from(e)).and_then(|t: Transaction| t.validate(schedule));
|
||||
fail_unless(test.find("transaction").is_none() == res.is_err());
|
||||
if let (Some(&Json::Object(ref tx)), Some(&Json::String(ref expect_sender))) = (test.find("transaction"), test.find("sender")) {
|
||||
let t = res.unwrap();
|
||||
fail_unless(t.sender().unwrap() == address_from_hex(clean(expect_sender)));
|
||||
fail_unless(t.data == bytes_from_json(&tx["data"]));
|
||||
fail_unless(t.gas == u256_from_json(&tx["gasLimit"]));
|
||||
fail_unless(t.gas_price == u256_from_json(&tx["gasPrice"]));
|
||||
fail_unless(t.nonce == u256_from_json(&tx["nonce"]));
|
||||
fail_unless(t.value == u256_from_json(&tx["value"]));
|
||||
if let Action::Call(ref to) = t.action {
|
||||
fail_unless(to == &address_from_json(&tx["to"]));
|
||||
} else {
|
||||
fail_unless(bytes_from_json(&tx["to"]).len() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
for f in failed.iter() {
|
||||
println!("FAILED: {:?}", f);
|
||||
}
|
||||
failed
|
||||
}
|
||||
|
||||
// Once we have interpolate idents.
|
||||
/*macro_rules! declare_test {
|
||||
($test_set_name: ident / $name: ident) => {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() {
|
||||
assert!(do_json_test(include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($name), ".json"))).len() == 0);
|
||||
}
|
||||
};
|
||||
($test_set_name: ident / $prename: ident / $name: ident) => {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
interpolate_idents! { fn [$prename _ $name]()
|
||||
{
|
||||
let json = include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($prename), "/", stringify!($name), ".json"));
|
||||
assert!(do_json_test(json).len() == 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare_test!{TransactionTests/ttTransactionTest}
|
||||
declare_test!{TransactionTests/tt10mbDataField}
|
||||
declare_test!{TransactionTests/ttWrongRLPTransaction}
|
||||
declare_test!{TransactionTests/Homestead/ttTransactionTest}
|
||||
declare_test!{TransactionTests/Homestead/tt10mbDataField}
|
||||
declare_test!{TransactionTests/Homestead/ttWrongRLPTransaction}
|
||||
declare_test!{TransactionTests/RandomTests/tr201506052141PYTHON}*/
|
||||
|
||||
declare_test!{TransactionTests_ttTransactionTest, "TransactionTests/ttTransactionTest"}
|
||||
declare_test_ignore!{TransactionTests_tt10mbDataField, "TransactionTests/tt10mbDataField"}
|
||||
declare_test!{TransactionTests_ttWrongRLPTransaction, "TransactionTests/ttWrongRLPTransaction"}
|
||||
declare_test!{TransactionTests_Homestead_ttTransactionTest, "TransactionTests/Homestead/ttTransactionTest"}
|
||||
declare_test_ignore!{TransactionTests_Homestead_tt10mbDataField, "TransactionTests/Homestead/tt10mbDataField"}
|
||||
declare_test!{TransactionTests_Homestead_ttWrongRLPTransaction, "TransactionTests/Homestead/ttWrongRLPTransaction"}
|
||||
declare_test!{TransactionTests_RandomTests_tr201506052141PYTHON, "TransactionTests/RandomTests/tr201506052141PYTHON"}
|
@ -23,10 +23,58 @@ pub struct Transaction {
|
||||
pub r: U256,
|
||||
pub s: U256,
|
||||
|
||||
hash: RefCell<Option<H256>>, //TODO: make this private
|
||||
hash: RefCell<Option<H256>>,
|
||||
sender: RefCell<Option<Address>>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Create a new message-call transaction.
|
||||
pub fn new_call(to: Address, value: U256, data: Bytes, gas: U256, gas_price: U256, nonce: U256) -> Transaction {
|
||||
Transaction {
|
||||
nonce: nonce,
|
||||
gas_price: gas_price,
|
||||
gas: gas,
|
||||
action: Action::Call(to),
|
||||
value: value,
|
||||
data: data,
|
||||
v: 0,
|
||||
r: U256::zero(),
|
||||
s: U256::zero(),
|
||||
hash: RefCell::new(None),
|
||||
sender: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new contract-creation transaction.
|
||||
pub fn new_create(value: U256, data: Bytes, gas: U256, gas_price: U256, nonce: U256) -> Transaction {
|
||||
Transaction {
|
||||
nonce: nonce,
|
||||
gas_price: gas_price,
|
||||
gas: gas,
|
||||
action: Action::Create,
|
||||
value: value,
|
||||
data: data,
|
||||
v: 0,
|
||||
r: U256::zero(),
|
||||
s: U256::zero(),
|
||||
hash: RefCell::new(None),
|
||||
sender: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the nonce of the transaction.
|
||||
pub fn nonce(&self) -> &U256 { &self.nonce }
|
||||
/// Get the gas price of the transaction.
|
||||
pub fn gas_price(&self) -> &U256 { &self.gas_price }
|
||||
/// Get the gas of the transaction.
|
||||
pub fn gas(&self) -> &U256 { &self.gas }
|
||||
/// Get the action of the transaction (Create or Call).
|
||||
pub fn action(&self) -> &Action { &self.action }
|
||||
/// Get the value of the transaction.
|
||||
pub fn value(&self) -> &U256 { &self.value }
|
||||
/// Get the data of the transaction.
|
||||
pub fn data(&self) -> &Bytes { &self.data }
|
||||
|
||||
/// Append object into RLP stream, optionally with or without the signature.
|
||||
pub fn rlp_append_opt(&self, s: &mut RlpStream, with_seal: Seal) {
|
||||
s.append_list(6 + match with_seal { Seal::With => 3, _ => 0 });
|
||||
@ -75,9 +123,6 @@ impl Transaction {
|
||||
*self.hash.borrow_mut() = None;
|
||||
}
|
||||
|
||||
/// Returns transaction type.
|
||||
pub fn action(&self) -> &Action { &self.action }
|
||||
|
||||
/// 0 is `v` is 27, 1 if 28, and 4 otherwise.
|
||||
pub fn standard_v(&self) -> u8 { match self.v { 27 => 0, 28 => 1, _ => 4 } }
|
||||
|
||||
@ -88,27 +133,47 @@ impl Transaction {
|
||||
pub fn message_hash(&self) -> H256 { self.rlp_bytes_opt(Seal::Without).sha3() }
|
||||
|
||||
/// Returns transaction sender.
|
||||
pub fn sender(&self) -> Result<Address, Error> { Ok(From::from(try!(ec::recover(&self.signature(), &self.message_hash())).sha3())) }
|
||||
pub fn sender(&self) -> Result<Address, Error> {
|
||||
let mut sender = self.sender.borrow_mut();
|
||||
match &mut *sender {
|
||||
&mut Some(ref h) => Ok(h.clone()),
|
||||
sender @ &mut None => {
|
||||
*sender = Some(From::from(try!(ec::recover(&self.signature(), &self.message_hash())).sha3()));
|
||||
Ok(sender.as_ref().unwrap().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs the transaction as coming from `sender`.
|
||||
pub fn sign(&mut self, secret: &Secret) {
|
||||
let sig = ec::sign(secret, &self.message_hash());
|
||||
let (r, s, v) = sig.unwrap().to_rsv();
|
||||
self.r = r;
|
||||
self.s = s;
|
||||
self.v = v + 27;
|
||||
}
|
||||
|
||||
/// Signs the transaction as coming from `sender`.
|
||||
pub fn signed(self, secret: &Secret) -> Transaction { let mut r = self; r.sign(secret); r }
|
||||
|
||||
/// Get the transaction cost in gas for the given params.
|
||||
pub fn gas_required_for(is_create: bool, data: &[u8], schedule: &Schedule) -> U256 {
|
||||
// CRITICAL TODO XXX FIX NEED BIGINT!!!!!
|
||||
pub fn gas_required_for(is_create: bool, data: &[u8], schedule: &Schedule) -> u64 {
|
||||
data.iter().fold(
|
||||
U256::from(if is_create {schedule.tx_create_gas} else {schedule.tx_gas}),
|
||||
|g, b| g + U256::from(match *b { 0 => schedule.tx_data_zero_gas, _ => schedule.tx_data_non_zero_gas})
|
||||
(if is_create {schedule.tx_create_gas} else {schedule.tx_gas}) as u64,
|
||||
|g, b| g + (match *b { 0 => schedule.tx_data_zero_gas, _ => schedule.tx_data_non_zero_gas }) as u64
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the transaction cost in gas for this transaction.
|
||||
pub fn gas_required(&self, schedule: &Schedule) -> U256 {
|
||||
pub fn gas_required(&self, schedule: &Schedule) -> u64 {
|
||||
Self::gas_required_for(match self.action{Action::Create=>true, Action::Call(_)=>false}, &self.data, schedule)
|
||||
}
|
||||
|
||||
/// Do basic validation, checking for valid signature and minimum gas,
|
||||
pub fn validate(self, schedule: &Schedule) -> Result<Transaction, Error> {
|
||||
try!(self.sender());
|
||||
if self.gas < self.gas_required(&schedule) {
|
||||
Err(From::from(TransactionError::InvalidGasLimit(OutOfBounds{min: Some(self.gas_required(&schedule)), max: None, found: self.gas})))
|
||||
if self.gas < U256::from(self.gas_required(&schedule)) {
|
||||
Err(From::from(TransactionError::InvalidGasLimit(OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas})))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
@ -142,113 +207,29 @@ impl Decodable for Transaction {
|
||||
v: try!(u16::decode(&d[6])) as u8,
|
||||
r: try!(Decodable::decode(&d[7])),
|
||||
s: try!(Decodable::decode(&d[8])),
|
||||
hash: RefCell::new(None)
|
||||
hash: RefCell::new(None),
|
||||
sender: RefCell::new(None),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean(s: &str) -> &str {
|
||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
||||
&s[2..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
#[test]
|
||||
fn sender_test() {
|
||||
let t: Transaction = decode(&FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap());
|
||||
assert_eq!(t.data, b"");
|
||||
assert_eq!(t.gas, U256::from(0x5208u64));
|
||||
assert_eq!(t.gas_price, U256::from(0x01u64));
|
||||
assert_eq!(t.nonce, U256::from(0x00u64));
|
||||
if let Action::Call(ref to) = t.action {
|
||||
assert_eq!(*to, address_from_hex("095e7baea6a6c7c4c2dfeb977efac326af552d87"));
|
||||
} else { panic!(); }
|
||||
assert_eq!(t.value, U256::from(0x0au64));
|
||||
assert_eq!(t.sender().unwrap(), address_from_hex("0f65fe9276bc9a24ae7083ae28e2660ef72df99e"));
|
||||
}
|
||||
|
||||
pub fn bytes_from_json(json: &Json) -> Bytes {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() % 2 == 1 {
|
||||
FromHex::from_hex(&("0".to_string() + &(clean(s).to_string()))[..]).unwrap_or(vec![])
|
||||
} else {
|
||||
FromHex::from_hex(clean(s)).unwrap_or(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address_from_json(json: &Json) -> Address {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() % 2 == 1 {
|
||||
address_from_hex(&("0".to_string() + &(clean(s).to_string()))[..])
|
||||
} else {
|
||||
address_from_hex(clean(s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u256_from_json(json: &Json) -> U256 {
|
||||
let s = json.as_string().unwrap();
|
||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
||||
// hex
|
||||
U256::from_str(&s[2..]).unwrap()
|
||||
}
|
||||
else {
|
||||
// dec
|
||||
U256::from_dec_str(s).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use util::*;
|
||||
use evm::Schedule;
|
||||
use header::BlockNumber;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sender_test() {
|
||||
let t: Transaction = decode(&FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap());
|
||||
assert_eq!(t.data, b"");
|
||||
assert_eq!(t.gas, U256::from(0x5208u64));
|
||||
assert_eq!(t.gas_price, U256::from(0x01u64));
|
||||
assert_eq!(t.nonce, U256::from(0x00u64));
|
||||
if let Action::Call(ref to) = t.action {
|
||||
assert_eq!(*to, address_from_hex("095e7baea6a6c7c4c2dfeb977efac326af552d87"));
|
||||
} else { panic!(); }
|
||||
assert_eq!(t.value, U256::from(0x0au64));
|
||||
assert_eq!(t.sender().unwrap(), address_from_hex("0f65fe9276bc9a24ae7083ae28e2660ef72df99e"));
|
||||
}
|
||||
|
||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||
let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid");
|
||||
let mut failed = Vec::new();
|
||||
let schedule = Schedule::new_frontier();
|
||||
for (name, test) in json.as_object().unwrap() {
|
||||
let mut fail = false;
|
||||
let mut fail_unless = |cond: bool| if !cond && fail { failed.push(name.to_string()); fail = true };
|
||||
let _ = BlockNumber::from_str(test["blocknumber"].as_string().unwrap()).unwrap();
|
||||
let rlp = bytes_from_json(&test["rlp"]);
|
||||
let res = UntrustedRlp::new(&rlp).as_val().map_err(|e| From::from(e)).and_then(|t: Transaction| t.validate(&schedule));
|
||||
fail_unless(test.find("transaction").is_none() == res.is_err());
|
||||
if let (Some(&Json::Object(ref tx)), Some(&Json::String(ref expect_sender))) = (test.find("transaction"), test.find("sender")) {
|
||||
let t = res.unwrap();
|
||||
fail_unless(t.sender().unwrap() == address_from_hex(clean(expect_sender)));
|
||||
fail_unless(t.data == bytes_from_json(&tx["data"]));
|
||||
fail_unless(t.gas == u256_from_json(&tx["gasLimit"]));
|
||||
fail_unless(t.gas_price == u256_from_json(&tx["gasPrice"]));
|
||||
fail_unless(t.nonce == u256_from_json(&tx["nonce"]));
|
||||
fail_unless(t.value == u256_from_json(&tx["value"]));
|
||||
if let Action::Call(ref to) = t.action {
|
||||
fail_unless(to == &address_from_json(&tx["to"]));
|
||||
} else {
|
||||
fail_unless(bytes_from_json(&tx["to"]).len() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
for f in failed.iter() {
|
||||
println!("FAILED: {:?}", f);
|
||||
}
|
||||
failed
|
||||
}
|
||||
|
||||
macro_rules! declare_test {
|
||||
($test_set_name: ident/$name: ident) => {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() {
|
||||
assert!(do_json_test(include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($name), ".json"))).len() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_test!{TransactionTests/ttTransactionTest}
|
||||
declare_test!{TransactionTests/tt10mbDataField}
|
||||
declare_test!{TransactionTests/ttWrongRLPTransaction}
|
||||
#[test]
|
||||
fn signing() {
|
||||
let key = KeyPair::create().unwrap();
|
||||
let t = Transaction::new_create(U256::from(42u64), b"Hello!".to_vec(), U256::from(3000u64), U256::from(50_000u64), U256::from(1u64)).signed(&key.secret());
|
||||
assert_eq!(Address::from(key.public().sha3()), t.sender().unwrap());
|
||||
}
|
@ -7,17 +7,17 @@
|
||||
|
||||
use common::*;
|
||||
use engine::Engine;
|
||||
use blockchain::BlockChain;
|
||||
use blockchain::*;
|
||||
|
||||
/// 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> {
|
||||
let block = BlockView::new(bytes);
|
||||
let header = block.header();
|
||||
try!(verify_header(&header));
|
||||
try!(verify_header(&header, engine));
|
||||
try!(verify_block_integrity(bytes, &header.transactions_root, &header.uncles_hash));
|
||||
try!(engine.verify_block_basic(&header, Some(bytes)));
|
||||
for u in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::<Header>()) {
|
||||
try!(verify_header(&u));
|
||||
try!(verify_header(&u, engine));
|
||||
try!(engine.verify_block_basic(&u, None));
|
||||
}
|
||||
Ok(())
|
||||
@ -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.
|
||||
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 header = block.header();
|
||||
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>()) {
|
||||
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_parent.hash()) {
|
||||
return Err(From::from(BlockError::UncleInChain(uncle_parent.hash())))
|
||||
if excluded.contains(&uncle.hash()) {
|
||||
return Err(From::from(BlockError::UncleInChain(uncle.hash())))
|
||||
}
|
||||
|
||||
// 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^8
|
||||
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 {
|
||||
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() {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
@ -113,13 +119,21 @@ pub fn verify_block_final(bytes: &[u8], engine: &Engine, bc: &BlockChain) -> Res
|
||||
}
|
||||
|
||||
/// Check basic header parameters.
|
||||
fn verify_header(header: &Header) -> Result<(), Error> {
|
||||
if header.number > From::from(BlockNumber::max_value()) {
|
||||
fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> {
|
||||
if header.number >= From::from(BlockNumber::max_value()) {
|
||||
return Err(From::from(BlockError::InvalidNumber(OutOfBounds { max: Some(From::from(BlockNumber::max_value())), min: None, found: header.number })))
|
||||
}
|
||||
if header.gas_used > header.gas_limit {
|
||||
return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit), min: None, found: header.gas_used })));
|
||||
}
|
||||
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
|
||||
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 })));
|
||||
}
|
||||
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() })));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -152,3 +166,235 @@ fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &
|
||||
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.clone(), good_uncle2.clone() ];
|
||||
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: Some(min_gas_limit), max: None, 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: Some(BlockNumber::max_value()), min: None, 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: Some(header.gas_limit), min: None, 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: Some(engine.maximum_extra_data_size()), min: None, 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: Some(engine.maximum_extra_data_size()), min: None, 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: None, min: Some(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: None, min: Some(parent.number + 1), found: header.number }));
|
||||
|
||||
header = good.clone();
|
||||
let mut bad_uncles = good_uncles.clone();
|
||||
bad_uncles.push(good_uncle1.clone());
|
||||
check_fail(verify_block_final(&create_test_block_with_data(&header, &good_transactions, &bad_uncles), engine.deref(), &bc),
|
||||
TooManyUncles(OutOfBounds { max: Some(engine.maximum_uncle_count()), min: None, found: bad_uncles.len() }));
|
||||
|
||||
// TODO: some additional uncle checks
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user