diff --git a/.travis.yml b/.travis.yml index 227853669..675eb0be1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,11 +37,11 @@ after_success: | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && cmake .. && make && make install DESTDIR=../tmp && cd ../.. && cargo test --no-run ${KCOV_FEATURES} ${TARGETS} && - ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/deps/ethcore_util-* && - ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/deps/ethash-* && - ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/deps/ethcore-* && - ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/deps/ethsync-* && - ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/deps/ethcore_rpc-* && + ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov target/debug/deps/ethcore_util-* && + ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov target/debug/deps/ethash-* && + ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov target/debug/deps/ethcore-* && + ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov target/debug/deps/ethsync-* && + ./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests target/kcov target/debug/deps/ethcore_rpc-* && ./kcov-master/tmp/usr/local/bin/kcov --coveralls-id=${TRAVIS_JOB_ID} --exclude-pattern /.cargo,/root/.multirust target/kcov target/debug/parity-* && [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && diff --git a/cov.sh b/cov.sh index c63687acf..a1fa29e46 100755 --- a/cov.sh +++ b/cov.sh @@ -18,9 +18,9 @@ fi cargo test -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity --no-run || exit $? rm -rf target/coverage mkdir -p target/coverage -kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore-* -kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests --include-pattern src --verify target/coverage target/debug/deps/ethash-* -kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-* -kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests --include-pattern src --verify target/coverage target/debug/deps/ethsync-* -kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-* +kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore-* +kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests --include-pattern src --verify target/coverage target/debug/deps/ethash-* +kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-* +kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests --include-pattern src --verify target/coverage target/debug/deps/ethsync-* +kcov --exclude-pattern ~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-* xdg-open target/coverage/index.html diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index aa5a0c4bd..c36c35232 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -183,11 +183,11 @@ impl Account { #[cfg(test)] /// Determine whether there are any un-`commit()`-ed storage-setting operations. pub fn storage_is_clean(&self) -> bool { self.storage_overlay.borrow().iter().find(|&(_, &(f, _))| f == Filth::Dirty).is_none() } - + #[cfg(test)] /// return the storage root associated with this account or None if it has been altered via the overlay. pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} } - + /// return the storage overlay. pub fn storage_overlay(&self) -> Ref> { self.storage_overlay.borrow() } @@ -198,7 +198,11 @@ impl Account { pub fn add_balance(&mut self, x: &U256) { self.balance = self.balance + *x; } /// Increment the nonce of the account by one. - pub fn sub_balance(&mut self, x: &U256) { self.balance = self.balance - *x; } + /// Panics if balance is less than `x` + pub fn sub_balance(&mut self, x: &U256) { + assert!(self.balance >= *x); + self.balance = self.balance - *x; + } /// Commit the `storage_overlay` to the backing DB and update `storage_root`. pub fn commit_storage(&mut self, db: &mut AccountDBMut) { diff --git a/ethcore/src/blockchain.rs b/ethcore/src/blockchain.rs index ff1e508d3..764b76588 100644 --- a/ethcore/src/blockchain.rs +++ b/ethcore/src/blockchain.rs @@ -23,6 +23,24 @@ use extras::*; use transaction::*; use views::*; +/// Uniquely identifies block. +pub enum BlockId { + /// Block's sha3. + /// Querying by hash is always faster. + Hash(H256), + /// Block number within canon blockchain. + Number(BlockNumber) +} + +/// Uniquely identifies transaction. +pub enum TransactionId { + /// Transaction's sha3. + Hash(H256), + /// Block id and transaction index within this block. + /// Querying by block position is always faster. + Location(BlockId, usize) +} + /// Represents a tree route between `from` block and `to` block: pub struct TreeRoute { /// A vector of hashes of all blocks, ordered from `from` to `to`. @@ -111,19 +129,24 @@ pub trait BlockProvider { } /// Get transaction with given transaction hash. - fn transaction(&self, hash: &H256) -> Option { - self.transaction_address(hash).and_then(|address| self.transaction_at(&address)) - } - - /// Get transaction at given address. - fn transaction_at(&self, address: &TransactionAddress) -> Option { - self.block(&address.block_hash).map(|bytes| BlockView::new(&bytes).transactions()).and_then(|t| t.into_iter().nth(address.index)) + fn transaction(&self, id: TransactionId) -> Option { + match id { + TransactionId::Hash(ref hash) => self.transaction_address(hash), + TransactionId::Location(BlockId::Hash(hash), index) => Some(TransactionAddress { + block_hash: hash, + index: index + }), + TransactionId::Location(BlockId::Number(number), index) => self.block_hash(number).map(|hash| TransactionAddress { + block_hash: hash, + index: index + }) + }.and_then(|address| self.block(&address.block_hash).and_then(|bytes| BlockView::new(&bytes).localized_transaction_at(address.index))) } /// Get a list of transactions for a given block. - /// Returns None if block deos not exist. - fn transactions(&self, hash: &H256) -> Option> { - self.block(hash).map(|bytes| BlockView::new(&bytes).transactions()) + /// Returns None if block does not exist. + fn transactions(&self, hash: &H256) -> Option> { + self.block(hash).map(|bytes| BlockView::new(&bytes).localized_transactions()) } /// Returns reference to genesis hash. @@ -864,7 +887,7 @@ mod tests { let transactions = bc.transactions(&b1_hash).unwrap(); assert_eq!(transactions.len(), 7); for t in transactions { - assert_eq!(bc.transaction(&t.hash()).unwrap(), t); + assert_eq!(bc.transaction(TransactionId::Hash(t.hash())).unwrap(), t); } } } diff --git a/ethcore/src/client.rs b/ethcore/src/client.rs index 11671b3f2..3de5c097e 100644 --- a/ethcore/src/client.rs +++ b/ethcore/src/client.rs @@ -18,7 +18,7 @@ use util::*; use rocksdb::{Options, DB, DBCompactionStyle}; -use blockchain::{BlockChain, BlockProvider, CacheSize}; +use blockchain::{BlockChain, BlockProvider, CacheSize, TransactionId}; use views::BlockView; use error::*; use header::BlockNumber; @@ -31,6 +31,7 @@ use service::{NetSyncMessage, SyncMessage}; use env_info::LastHashes; use verification::*; use block::*; +use transaction::LocalizedTransaction; pub use blockchain::TreeRoute; /// General block status @@ -104,6 +105,9 @@ pub trait BlockChainClient : Sync + Send { /// Get block total difficulty. fn block_total_difficulty_at(&self, n: BlockNumber) -> Option; + /// Get transaction with given hash. + fn transaction(&self, id: TransactionId) -> Option; + /// Get a tree route between `from` and `to`. /// See `BlockChain::tree_route`. fn tree_route(&self, from: &H256, to: &H256) -> Option; @@ -388,6 +392,10 @@ impl BlockChainClient for Client { self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_total_difficulty(&h)) } + fn transaction(&self, id: TransactionId) -> Option { + self.chain.read().unwrap().transaction(id) + } + fn tree_route(&self, from: &H256, to: &H256) -> Option { self.chain.read().unwrap().tree_route(from.clone(), to.clone()) } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 812dc3acd..782063cb2 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -43,11 +43,11 @@ pub struct Executed { pub gas: U256, /// Gas used during execution of transaction. pub gas_used: U256, - /// Gas refunded after the execution of transaction. + /// Gas refunded after the execution of transaction. /// To get gas that was required up front, add `refunded` and `gas_used`. pub refunded: U256, /// Cumulative gas used in current block so far. - /// + /// /// `cumulative_gas_used = gas_used(t0) + gas_used(t1) + ... gas_used(tn)` /// /// where `tn` is current transaction. @@ -56,9 +56,9 @@ pub struct Executed { pub logs: Vec, /// Addresses of contracts created during execution of transaction. /// Ordered from earliest creation. - /// - /// eg. sender creates contract A and A in constructor creates contract B - /// + /// + /// 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
} @@ -119,13 +119,13 @@ impl<'a> Executive<'a> { if t.nonce != nonce { return Err(From::from(ExecutionError::InvalidNonce { expected: nonce, got: t.nonce })); } - + // validate if transaction fits into given block if self.info.gas_used + t.gas > self.info.gas_limit { - return Err(From::from(ExecutionError::BlockGasLimitReached { - gas_limit: self.info.gas_limit, - gas_used: self.info.gas_used, - gas: t.gas + return Err(From::from(ExecutionError::BlockGasLimitReached { + gas_limit: self.info.gas_limit, + gas_used: self.info.gas_used, + gas: t.gas })); } @@ -220,7 +220,7 @@ impl<'a> Executive<'a> { if self.engine.is_builtin(¶ms.code_address) { // if destination is builtin, try to execute it - + let default = []; let data = if let Some(ref d) = params.data { d as &[u8] } else { &default as &[u8] }; @@ -239,7 +239,7 @@ impl<'a> Executive<'a> { } } else if params.code.is_some() { // if destination is a contract, do normal message call - + // part of substate that may be reverted let mut unconfirmed_substate = Substate::new(); @@ -258,7 +258,7 @@ impl<'a> Executive<'a> { Ok(params.gas) } } - + /// Creates contract with given contract params. /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). /// Modifies the substate. @@ -317,7 +317,7 @@ impl<'a> Executive<'a> { self.state.kill_account(address); } - match result { + match result { Err(evm::Error::Internal) => Err(ExecutionError::Internal), Err(_) => { Ok(Executed { @@ -345,8 +345,8 @@ impl<'a> Executive<'a> { fn enact_result(&mut self, result: &evm::Result, substate: &mut Substate, un_substate: Substate) { match *result { Err(evm::Error::OutOfGas) - | Err(evm::Error::BadJumpDestination {..}) - | Err(evm::Error::BadInstruction {.. }) + | Err(evm::Error::BadJumpDestination {..}) + | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::OutOfStack {..}) => { self.state.revert_snapshot(); @@ -364,42 +364,10 @@ impl<'a> Executive<'a> { mod tests { use super::*; use common::*; - use ethereum; - use engine::*; - use spec::*; - use evm::{Schedule, Factory, VMType}; + use evm::{Factory, VMType}; use substate::*; use tests::helpers::*; - struct TestEngine { - factory: Factory, - spec: Spec, - max_depth: usize - } - - impl TestEngine { - fn new(max_depth: usize, factory: Factory) -> TestEngine { - TestEngine { - factory: factory, - spec: ethereum::new_frontier_test(), - max_depth: max_depth - } - } - } - - impl Engine for TestEngine { - fn name(&self) -> &str { "TestEngine" } - fn spec(&self) -> &Spec { &self.spec } - fn vm_factory(&self) -> &Factory { - &self.factory - } - fn schedule(&self, _env_info: &EnvInfo) -> Schedule { - let mut schedule = Schedule::new_frontier(); - schedule.max_depth = self.max_depth; - schedule - } - } - #[test] fn test_contract_address() { let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); @@ -488,7 +456,7 @@ mod tests { let mut ex = Executive::new(&mut state, &info, &engine); ex.create(params, &mut substate).unwrap() }; - + assert_eq!(gas_left, U256::from(62_976)); // ended with max depth assert_eq!(substate.contracts_created.len(), 0); @@ -542,7 +510,7 @@ mod tests { let mut ex = Executive::new(&mut state, &info, &engine); ex.create(params, &mut substate).unwrap() }; - + assert_eq!(gas_left, U256::from(62_976)); assert_eq!(substate.contracts_created.len(), 0); } @@ -594,7 +562,7 @@ mod tests { let mut ex = Executive::new(&mut state, &info, &engine); ex.create(params, &mut substate).unwrap(); } - + assert_eq!(substate.contracts_created.len(), 1); assert_eq!(substate.contracts_created[0], next_address); } @@ -666,7 +634,7 @@ mod tests { fn test_recursive_bomb1(factory: Factory) { // 60 01 - push 1 // 60 00 - push 0 - // 54 - sload + // 54 - sload // 01 - add // 60 00 - push 0 // 55 - sstore @@ -766,7 +734,7 @@ mod tests { 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.") @@ -797,10 +765,10 @@ mod tests { let mut ex = Executive::new(&mut state, &info, &engine); ex.transact(&t) }; - + match res { - Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) - if expected == U256::zero() && got == U256::one() => (), + Err(Error::Execution(ExecutionError::InvalidNonce { expected, got })) + if expected == U256::zero() && got == U256::one() => (), _ => assert!(false, "Expected invalid nonce error.") } } @@ -832,8 +800,8 @@ mod tests { }; 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) => (), + 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.") } } @@ -863,10 +831,10 @@ mod tests { let mut ex = Executive::new(&mut state, &info, &engine); ex.transact(&t) }; - + match res { - Err(Error::Execution(ExecutionError::NotEnoughCash { required , got })) - if required == U512::from(100_018) && got == U512::from(100_017) => (), + Err(Error::Execution(ExecutionError::NotEnoughCash { required , got })) + if required == U512::from(100_018) && got == U512::from(100_017) => (), _ => assert!(false, "Expected not enough cash error. {:?}", res) } } diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index ad2f18f11..558e477c7 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -68,12 +68,12 @@ pub struct Externalities<'a> { impl<'a> Externalities<'a> { /// Basic `Externalities` constructor. - pub fn new(state: &'a mut State, - env_info: &'a EnvInfo, - engine: &'a Engine, + pub fn new(state: &'a mut State, + env_info: &'a EnvInfo, + engine: &'a Engine, depth: usize, origin_info: OriginInfo, - substate: &'a mut Substate, + substate: &'a mut Substate, output: OutputPolicy<'a>) -> Self { Externalities { state: state, @@ -106,16 +106,18 @@ impl<'a> Ext for Externalities<'a> { } fn blockhash(&self, number: &U256) -> H256 { + // TODO: comment out what this function expects from env_info, since it will produce panics if the latter is inconsistent match *number < U256::from(self.env_info.number) && number.low_u64() >= cmp::max(256, self.env_info.number) - 256 { true => { let index = self.env_info.number - number.low_u64() - 1; + assert!(index < self.env_info.last_hashes.len() as u64, format!("Inconsistent env_info, should contain at least {:?} last hashes", index+1)); let r = self.env_info.last_hashes[index as usize].clone(); trace!("ext: blockhash({}) -> {} self.env_info.number={}\n", number, r, self.env_info.number); r }, false => { trace!("ext: blockhash({}) -> null self.env_info.number={}\n", number, self.env_info.number); - H256::from(&U256::zero()) + H256::zero() }, } } @@ -139,7 +141,7 @@ impl<'a> Ext for Externalities<'a> { self.state.inc_nonce(&self.origin_info.address); let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.depth); - + // TODO: handle internal error separately match ex.create(params, self.substate) { Ok(gas_left) => { @@ -150,18 +152,18 @@ impl<'a> Ext for Externalities<'a> { } } - fn call(&mut self, - gas: &U256, - sender_address: &Address, - receive_address: &Address, + fn call(&mut self, + gas: &U256, + sender_address: &Address, + receive_address: &Address, value: Option, - data: &[u8], - code_address: &Address, + data: &[u8], + code_address: &Address, output: &mut [u8]) -> MessageCallResult { let mut params = ActionParams { sender: sender_address.clone(), - address: receive_address.clone(), + address: receive_address.clone(), value: ActionValue::Apparent(self.origin_info.value.clone()), code_address: code_address.clone(), origin: self.origin_info.origin.clone(), @@ -257,3 +259,144 @@ impl<'a> Ext for Externalities<'a> { self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one(); } } + +#[cfg(test)] +mod tests { + use common::*; + use state::*; + use engine::*; + use evm::{Ext}; + use substate::*; + use tests::helpers::*; + use super::*; + + fn get_test_origin() -> OriginInfo { + OriginInfo { + address: Address::zero(), + origin: Address::zero(), + gas_price: U256::zero(), + value: U256::zero() + } + } + + fn get_test_env_info() -> EnvInfo { + EnvInfo { + number: 100, + author: x!(0), + timestamp: 0, + difficulty: x!(0), + last_hashes: vec![], + gas_used: x!(0), + gas_limit: x!(0) + } + } + + struct TestSetup { + state: GuardedTempResult, + engine: Box, + sub_state: Substate, + env_info: EnvInfo + } + + impl TestSetup { + fn new() -> TestSetup { + TestSetup { + state: get_temp_state(), + engine: get_test_spec().to_engine().unwrap(), + sub_state: Substate::new(), + env_info: get_test_env_info() + } + } + } + + #[test] + fn can_be_created() { + let mut setup = TestSetup::new(); + let state = setup.state.reference_mut(); + + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + + assert_eq!(ext.env_info().number, 100); + } + + #[test] + fn can_return_block_hash_no_env() { + let mut setup = TestSetup::new(); + let state = setup.state.reference_mut(); + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + + let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap()); + + assert_eq!(hash, H256::zero()); + } + + #[test] + fn can_return_block_hash() { + let test_hash = H256::from("afafafafafafafafafafafbcbcbcbcbcbcbcbcbcbeeeeeeeeeeeeedddddddddd"); + let test_env_number = 0x120001; + + let mut setup = TestSetup::new(); + { + let env_info = &mut setup.env_info; + env_info.number = test_env_number; + env_info.last_hashes.push(test_hash.clone()); + } + let state = setup.state.reference_mut(); + let ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + + let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap()); + + assert_eq!(test_hash, hash); + } + + #[test] + #[should_panic] + fn can_call_fail_empty() { + let mut setup = TestSetup::new(); + let state = setup.state.reference_mut(); + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + + let mut output = vec![]; + + // this should panic because we have no balance on any account + ext.call( + &U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap(), + &Address::new(), + &Address::new(), + Some(U256::from_str("0000000000000000000000000000000000000000000000000000000000150000").unwrap()), + &vec![], + &Address::new(), + &mut output); + } + + #[test] + fn can_log() { + let log_data = vec![120u8, 110u8]; + let log_topics = vec![H256::from("af0fa234a6af46afa23faf23bcbc1c1cb4bcb7bcbe7e7e7ee3ee2edddddddddd")]; + + let mut setup = TestSetup::new(); + let state = setup.state.reference_mut(); + + { + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + ext.log(log_topics, &log_data); + } + + assert_eq!(setup.sub_state.logs.len(), 1); + } + + #[test] + fn can_suicide() { + let refund_account = &Address::new(); + + let mut setup = TestSetup::new(); + let state = setup.state.reference_mut(); + + { + let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract); + ext.suicide(&refund_account); + } + + assert_eq!(setup.sub_state.suicides.len(), 1); + } +} diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 7ac60e6b4..b08257a92 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -26,29 +26,29 @@ use externalities::*; use substate::*; use tests::helpers::*; -struct TestEngine { +struct TestEngineFrontier { vm_factory: Factory, spec: Spec, max_depth: usize } -impl TestEngine { - fn new(max_depth: usize, vm_type: VMType) -> TestEngine { - TestEngine { +impl TestEngineFrontier { + fn new(max_depth: usize, vm_type: VMType) -> TestEngineFrontier { + TestEngineFrontier { vm_factory: Factory::new(vm_type), spec: ethereum::new_frontier_test(), - max_depth: max_depth + max_depth: max_depth } } } -impl Engine for TestEngine { +impl Engine for TestEngineFrontier { fn name(&self) -> &str { "TestEngine" } fn spec(&self) -> &Spec { &self.spec } fn vm_factory(&self) -> &Factory { &self.vm_factory } - fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { let mut schedule = Schedule::new_frontier(); - schedule.max_depth = self.max_depth; + schedule.max_depth = self.max_depth; schedule } } @@ -69,12 +69,12 @@ struct TestExt<'a> { } impl<'a> TestExt<'a> { - fn new(state: &'a mut State, - info: &'a EnvInfo, - engine: &'a Engine, + fn new(state: &'a mut State, + info: &'a EnvInfo, + engine: &'a Engine, depth: usize, origin_info: OriginInfo, - substate: &'a mut Substate, + substate: &'a mut Substate, output: OutputPolicy<'a>, address: Address) -> Self { TestExt { @@ -116,13 +116,13 @@ impl<'a> Ext for TestExt<'a> { ContractCreateResult::Created(self.contract_address.clone(), *gas) } - fn call(&mut self, - gas: &U256, - _sender_address: &Address, - receive_address: &Address, + fn call(&mut self, + gas: &U256, + _sender_address: &Address, + receive_address: &Address, value: Option, - data: &[u8], - _code_address: &Address, + data: &[u8], + _code_address: &Address, _output: &mut [u8]) -> MessageCallResult { self.callcreates.push(CallCreate { data: data.to_vec(), @@ -136,7 +136,7 @@ impl<'a> Ext for TestExt<'a> { fn extcode(&self, address: &Address) -> Bytes { self.ext.extcode(address) } - + fn log(&mut self, topics: Vec, data: &[u8]) { self.ext.log(topics, data) } @@ -185,11 +185,11 @@ fn do_json_test_for(vm: &VMType, json_data: &[u8]) -> Vec { // ::std::io::stdout().flush(); let mut fail = false; //let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.to_string()); fail = true }; - let mut fail_unless = |cond: bool, s: &str | if !cond && !fail { - failed.push(format!("[{}] {}: {}", vm, name, s)); - fail = true + let mut fail_unless = |cond: bool, s: &str | if !cond && !fail { + failed.push(format!("[{}] {}: {}", vm, name, s)); + fail = true }; - + // test env let mut state_result = get_temp_state(); let mut state = state_result.reference_mut(); @@ -209,7 +209,7 @@ fn do_json_test_for(vm: &VMType, json_data: &[u8]) -> Vec { EnvInfo::from_json(env) }).unwrap_or_default(); - let engine = TestEngine::new(1, vm.clone()); + let engine = TestEngineFrontier::new(1, vm.clone()); // params let mut params = ActionParams::default(); @@ -226,18 +226,18 @@ fn do_json_test_for(vm: &VMType, json_data: &[u8]) -> Vec { let out_of_gas = test.find("callcreates").map(|_calls| { }).is_none(); - + let mut substate = Substate::new(); let mut output = vec![]; // execute let (res, callcreates) = { - let mut ex = TestExt::new(&mut state, - &info, - &engine, - 0, - OriginInfo::from(¶ms), - &mut substate, + let mut ex = TestExt::new(&mut state, + &info, + &engine, + 0, + OriginInfo::from(¶ms), + &mut substate, OutputPolicy::Return(BytesRef::Flexible(&mut output)), params.address.clone()); let evm = engine.vm_factory().create(); diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 9537c8862..6c4535339 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -98,6 +98,7 @@ pub mod ethereum; pub mod header; pub mod service; pub mod spec; +pub mod transaction; pub mod views; pub mod receipt; @@ -115,7 +116,6 @@ mod state; mod account; mod account_db; mod action_params; -mod transaction; mod null_engine; mod builtin; mod extras; diff --git a/ethcore/src/substate.rs b/ethcore/src/substate.rs index f42ea38fd..235ce2e97 100644 --- a/ethcore/src/substate.rs +++ b/ethcore/src/substate.rs @@ -56,6 +56,12 @@ mod tests { use super::*; use common::*; + #[test] + fn created() { + let sub_state = Substate::new(); + assert_eq!(sub_state.suicides.len(), 0); + } + #[test] fn accrue() { let mut sub_state = Substate::new(); diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index f5815b718..93e3e0a0d 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -23,7 +23,9 @@ use std::fs::{remove_dir_all}; use blockchain::{BlockChain}; use state::*; use rocksdb::*; - +use evm::{Schedule, Factory}; +use engine::*; +use ethereum; #[cfg(feature = "json-tests")] pub enum ChainEra { @@ -81,6 +83,35 @@ impl GuardedTempResult { } } +pub struct TestEngine { + factory: Factory, + spec: Spec, + max_depth: usize +} + +impl TestEngine { + pub fn new(max_depth: usize, factory: Factory) -> TestEngine { + TestEngine { + factory: factory, + spec: ethereum::new_frontier_test(), + max_depth: max_depth + } + } +} + +impl Engine for TestEngine { + fn name(&self) -> &str { "TestEngine" } + fn spec(&self) -> &Spec { &self.spec } + fn vm_factory(&self) -> &Factory { + &self.factory + } + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + let mut schedule = Schedule::new_frontier(); + schedule.max_depth = self.max_depth; + schedule + } +} + pub fn get_test_spec() -> Spec { Spec::new_test() } diff --git a/ethcore/src/transaction.rs b/ethcore/src/transaction.rs index 119f565dd..b43c271d3 100644 --- a/ethcore/src/transaction.rs +++ b/ethcore/src/transaction.rs @@ -19,6 +19,7 @@ use util::*; use error::*; use evm::Schedule; +use header::BlockNumber; #[derive(Debug, Clone, PartialEq, Eq)] /// Transaction action type. @@ -156,8 +157,7 @@ impl Transaction { } } - - +/// Signed transaction information. #[derive(Debug, Clone, Eq)] pub struct SignedTransaction { /// Plain Transaction. @@ -290,6 +290,27 @@ impl SignedTransaction { } } +/// Signed Transaction that is a part of canon blockchain. +#[derive(Debug, PartialEq, Eq)] +pub struct LocalizedTransaction { + /// Signed part. + pub signed: SignedTransaction, + /// Block number. + pub block_number: BlockNumber, + /// Block hash. + pub block_hash: H256, + /// Transaction index within block. + pub transaction_index: usize +} + +impl Deref for LocalizedTransaction { + type Target = SignedTransaction; + + fn deref(&self) -> &Self::Target { + &self.signed + } +} + #[test] fn sender_test() { let t: SignedTransaction = decode(&FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap()); diff --git a/ethcore/src/views.rs b/ethcore/src/views.rs index 5b6b56ea5..4a7ff054d 100644 --- a/ethcore/src/views.rs +++ b/ethcore/src/views.rs @@ -155,6 +155,22 @@ impl<'a> BlockView<'a> { self.rlp.val_at(1) } + /// Return List of transactions with additional localization info. + pub fn localized_transactions(&self) -> Vec { + let header = self.header_view(); + let block_hash = header.sha3(); + let block_number = header.number(); + self.transactions() + .into_iter() + .enumerate() + .map(|(i, t)| LocalizedTransaction { + signed: t, + block_hash: block_hash.clone(), + block_number: block_number, + transaction_index: i + }).collect() + } + /// Return number of transactions in given block, without deserializing them. pub fn transactions_count(&self) -> usize { self.rlp.at(1).iter().count() @@ -170,6 +186,24 @@ impl<'a> BlockView<'a> { self.rlp.at(1).iter().map(|rlp| rlp.as_raw().sha3()).collect() } + /// Returns transaction at given index without deserializing unnecessary data. + pub fn transaction_at(&self, index: usize) -> Option { + self.rlp.at(1).iter().nth(index).map(|rlp| rlp.as_val()) + } + + /// Returns localized transaction at given index. + pub fn localized_transaction_at(&self, index: usize) -> Option { + let header = self.header_view(); + let block_hash = header.sha3(); + let block_number = header.number(); + self.transaction_at(index).map(|t| LocalizedTransaction { + signed: t, + block_hash: block_hash, + block_number: block_number, + transaction_index: index + }) + } + /// Return list of uncles of given block. pub fn uncles(&self) -> Vec
{ self.rlp.val_at(2) diff --git a/parity/main.rs b/parity/main.rs index 7fd8fb029..9badc9243 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -110,14 +110,14 @@ fn main() { let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit()); if args.flag_version { - println!(" + println!("\ Parity version {} ({}-{}-{}) Copyright 2015, 2016 Ethcore (UK) Limited License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. -By Wood/Paronyan/Kotewicz/Drwięga/Volf. +By Wood/Paronyan/Kotewicz/Drwięga/Volf.\ ", env!("CARGO_PKG_VERSION"), Target::arch(), Target::env(), Target::os()); return; } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 606a1ba6d..5d60b40a6 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -22,8 +22,9 @@ use util::uint::*; use util::sha3::*; use ethcore::client::*; use ethcore::views::*; +use ethcore::blockchain::{BlockId, TransactionId}; use v1::traits::{Eth, EthFilter}; -use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus}; +use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, Transaction, OptionalValue, Index}; /// Eth rpc implementation. pub struct EthClient { @@ -96,40 +97,35 @@ impl Eth for EthClient { } fn block_transaction_count(&self, params: Params) -> Result { - match from_params::(params) { - Ok(hash) => match self.client.block(&hash) { + from_params::<(H256,)>(params) + .and_then(|(hash,)| match self.client.block(&hash) { Some(bytes) => to_value(&BlockView::new(&bytes).transactions_count()), None => Ok(Value::Null) - }, - Err(err) => Err(err) - } + }) } fn block_uncles_count(&self, params: Params) -> Result { - match from_params::(params) { - Ok(hash) => match self.client.block(&hash) { + from_params::<(H256,)>(params) + .and_then(|(hash,)| match self.client.block(&hash) { Some(bytes) => to_value(&BlockView::new(&bytes).uncles_count()), None => Ok(Value::Null) - }, - Err(err) => Err(err) - } + }) } // TODO: do not ignore block number param fn code_at(&self, params: Params) -> Result { - match from_params::<(Address, BlockNumber)>(params) { - Ok((address, _block_number)) => to_value(&self.client.code(&address).map_or_else(Bytes::default, Bytes::new)), - Err(err) => Err(err) - } + from_params::<(Address, BlockNumber)>(params) + .and_then(|(address, _block_number)| to_value(&self.client.code(&address).map_or_else(Bytes::default, Bytes::new))) } fn block(&self, params: Params) -> Result { - match from_params::<(H256, bool)>(params) { - Ok((hash, include_txs)) => match (self.client.block_header(&hash), self.client.block_total_difficulty(&hash)) { + from_params::<(H256, bool)>(params) + .and_then(|(hash, include_txs)| match (self.client.block(&hash), self.client.block_total_difficulty(&hash)) { (Some(bytes), Some(total_difficulty)) => { - let view = HeaderView::new(&bytes); + let block_view = BlockView::new(&bytes); + let view = block_view.header_view(); let block = Block { - hash: view.sha3(), + hash: OptionalValue::Value(view.sha3()), parent_hash: view.parent_hash(), uncles_hash: view.uncles_hash(), author: view.author(), @@ -137,7 +133,7 @@ impl Eth for EthClient { state_root: view.state_root(), transactions_root: view.transactions_root(), receipts_root: view.receipts_root(), - number: U256::from(view.number()), + number: OptionalValue::Value(U256::from(view.number())), gas_used: view.gas_used(), gas_limit: view.gas_limit(), logs_bloom: view.log_bloom(), @@ -147,9 +143,9 @@ impl Eth for EthClient { uncles: vec![], transactions: { if include_txs { - BlockTransactions::Hashes(vec![]) + BlockTransactions::Full(block_view.localized_transactions().into_iter().map(From::from).collect()) } else { - BlockTransactions::Full(vec![]) + BlockTransactions::Hashes(block_view.transaction_hashes()) } }, extra_data: Bytes::default() @@ -157,12 +153,31 @@ impl Eth for EthClient { to_value(&block) }, _ => Ok(Value::Null) - }, - Err(err) => Err(err) - } + }) + } + + fn transaction_by_hash(&self, params: Params) -> Result { + from_params::<(H256,)>(params) + .and_then(|(hash,)| match self.client.transaction(TransactionId::Hash(hash)) { + Some(t) => to_value(&Transaction::from(t)), + None => Ok(Value::Null) + }) + } + + fn transaction_by_block_hash_and_index(&self, params: Params) -> Result { + from_params::<(H256, Index)>(params) + .and_then(|(hash, index)| match self.client.transaction(TransactionId::Location(BlockId::Hash(hash), index.value())) { + Some(t) => to_value(&Transaction::from(t)), + None => Ok(Value::Null) + }) + } + + fn transaction_by_block_number_and_index(&self, _params: Params) -> Result { + unimplemented!() } } + /// Eth filter rpc implementation. pub struct EthFilterClient { client: Arc diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index d247e2174..640af1f82 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -74,8 +74,14 @@ pub trait Eth: Sized + Send + Sync + 'static { /// Estimate gas needed for execution of given contract. fn estimate_gas(&self, _: Params) -> Result { rpc_unimplemented!() } - /// Returns transaction at given block and index. - fn transaction_at(&self, _: Params) -> Result { rpc_unimplemented!() } + /// Get transaction by it's hash. + fn transaction_by_hash(&self, _: Params) -> Result { rpc_unimplemented!() } + + /// Returns transaction at given block hash and index. + fn transaction_by_block_hash_and_index(&self, _: Params) -> Result { rpc_unimplemented!() } + + /// Returns transaction by given block number and index. + fn transaction_by_block_number_and_index(&self, _: Params) -> Result { rpc_unimplemented!() } /// Returns transaction receipt. fn transaction_receipt(&self, _: Params) -> Result { rpc_unimplemented!() } @@ -131,8 +137,9 @@ pub trait Eth: Sized + Send + Sync + 'static { delegate.add_method("eth_estimateGas", Eth::estimate_gas); delegate.add_method("eth_getBlockByHash", Eth::block); delegate.add_method("eth_getBlockByNumber", Eth::block); - delegate.add_method("eth_getTransactionByBlockHashAndIndex", Eth::transaction_at); - delegate.add_method("eth_getTransactionByBlockNumberAndIndex", Eth::transaction_at); + delegate.add_method("eth_getTransactionByHash", Eth::transaction_by_hash); + delegate.add_method("eth_getTransactionByBlockHashAndIndex", Eth::transaction_by_block_hash_and_index); + delegate.add_method("eth_getTransactionByBlockNumberAndIndex", Eth::transaction_by_block_number_and_index); delegate.add_method("eth_getTransactionReceipt", Eth::transaction_receipt); delegate.add_method("eth_getUncleByBlockHashAndIndex", Eth::uncle_at); delegate.add_method("eth_getUncleByBlockNumberAndIndex", Eth::uncle_at); diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 59cafcf60..b92111bcb 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -17,7 +17,7 @@ use serde::{Serialize, Serializer}; use util::hash::*; use util::uint::*; -use v1::types::{Bytes, Transaction}; +use v1::types::{Bytes, Transaction, OptionalValue}; #[derive(Debug)] pub enum BlockTransactions { @@ -37,7 +37,7 @@ impl Serialize for BlockTransactions { #[derive(Debug, Serialize)] pub struct Block { - pub hash: H256, + pub hash: OptionalValue, #[serde(rename="parentHash")] pub parent_hash: H256, #[serde(rename="sha3Uncles")] @@ -51,7 +51,7 @@ pub struct Block { pub transactions_root: H256, #[serde(rename="receiptsRoot")] pub receipts_root: H256, - pub number: U256, + pub number: OptionalValue, #[serde(rename="gasUsed")] pub gas_used: U256, #[serde(rename="gasLimit")] @@ -73,14 +73,14 @@ mod tests { use serde_json; use util::hash::*; use util::uint::*; - use v1::types::{Transaction, Bytes}; + use v1::types::{Transaction, Bytes, OptionalValue}; use super::*; #[test] fn test_serialize_block_transactions() { let t = BlockTransactions::Full(vec![Transaction::default()]); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x00","transactionIndex":"0x00","from":"0x0000000000000000000000000000000000000000","to":"0x0000000000000000000000000000000000000000","value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x00"}]"#); + assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x00"}]"#); let t = BlockTransactions::Hashes(vec![H256::default()]); let serialized = serde_json::to_string(&t).unwrap(); @@ -90,7 +90,7 @@ mod tests { #[test] fn test_serialize_block() { let block = Block { - hash: H256::default(), + hash: OptionalValue::Value(H256::default()), parent_hash: H256::default(), uncles_hash: H256::default(), author: Address::default(), @@ -98,7 +98,7 @@ mod tests { state_root: H256::default(), transactions_root: H256::default(), receipts_root: H256::default(), - number: U256::default(), + number: OptionalValue::Value(U256::default()), gas_used: U256::default(), gas_limit: U256::default(), extra_data: Bytes::default(), diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs new file mode 100644 index 000000000..9b21cf8e7 --- /dev/null +++ b/rpc/src/v1/types/filter.rs @@ -0,0 +1,88 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use serde::{Deserialize, Deserializer, Error}; +use serde_json::value; +use jsonrpc_core::Value; +use util::hash::*; +use v1::types::BlockNumber; + +#[derive(Debug, PartialEq)] +pub enum Topic { + Single(H256), + Multiple(Vec), + Null +} + +impl Deserialize for Topic { + fn deserialize(deserializer: &mut D) -> Result + where D: Deserializer { + let v = try!(Value::deserialize(deserializer)); + + if v.is_null() { + return Ok(Topic::Null); + } + + Deserialize::deserialize(&mut value::Deserializer::new(v.clone())).map(Topic::Single) + .or_else(|_| Deserialize::deserialize(&mut value::Deserializer::new(v.clone())).map(Topic::Multiple)) + .map_err(|_| Error::syntax("")) // unreachable, but types must match + } +} + +#[derive(Debug, PartialEq, Deserialize)] +pub struct Filter { + #[serde(rename="fromBlock")] + pub from_block: Option, + #[serde(rename="toBlock")] + pub to_block: Option, + pub address: Option
, + pub topics: Option> +} + +#[cfg(test)] +mod tests { + use serde_json; + use std::str::FromStr; + use util::hash::*; + use super::*; + use v1::types::BlockNumber; + + #[test] + fn topic_deserialization() { + let s = r#"["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", null, ["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc"]]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, vec![ + Topic::Single(H256::from_str("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap()), + Topic::Null, + Topic::Multiple(vec![ + H256::from_str("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(), + H256::from_str("0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc").unwrap() + ]) + ]); + } + + #[test] + fn filter_deserialization() { + let s = r#"{"fromBlock":"earliest","toBlock":"latest"}"#; + let deserialized: Filter = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, Filter { + from_block: Some(BlockNumber::Earliest), + to_block: Some(BlockNumber::Latest), + address: None, + topics: None + }); + } +} diff --git a/rpc/src/v1/types/index.rs b/rpc/src/v1/types/index.rs new file mode 100644 index 000000000..a77096fbf --- /dev/null +++ b/rpc/src/v1/types/index.rs @@ -0,0 +1,66 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use serde::{Deserialize, Deserializer, Error}; +use serde::de::Visitor; + +/// Represents usize. +#[derive(Debug, PartialEq)] +pub struct Index(usize); + +impl Index { + pub fn value(&self) -> usize { + self.0 + } +} + +impl Deserialize for Index { + fn deserialize(deserializer: &mut D) -> Result + where D: Deserializer { + deserializer.visit(IndexVisitor) + } +} + +struct IndexVisitor; + +impl Visitor for IndexVisitor { + type Value = Index; + + fn visit_str(&mut self, value: &str) -> Result where E: Error { + match value { + _ if value.starts_with("0x") => usize::from_str_radix(&value[2..], 16).map(Index).map_err(|_| Error::syntax("invalid index")), + _ => value.parse::().map(Index).map_err(|_| Error::syntax("invalid index")) + } + } + + fn visit_string(&mut self, value: String) -> Result where E: Error { + self.visit_str(value.as_ref()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + + #[test] + fn block_number_deserialization() { + let s = r#"["0xa", "10"]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, vec![Index(10), Index(10)]); + } +} + diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 2286c69a1..bdbd157ff 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -17,11 +17,17 @@ mod block; mod block_number; mod bytes; +mod filter; +mod index; +mod optionals; mod sync; mod transaction; pub use self::block::{Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::bytes::Bytes; +pub use self::filter::Filter; +pub use self::index::Index; +pub use self::optionals::OptionalValue; pub use self::sync::SyncStatus; pub use self::transaction::Transaction; diff --git a/rpc/src/v1/types/optionals.rs b/rpc/src/v1/types/optionals.rs new file mode 100644 index 000000000..5db272251 --- /dev/null +++ b/rpc/src/v1/types/optionals.rs @@ -0,0 +1,58 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use serde::{Serialize, Serializer}; +use serde_json::Value; + +#[derive(Debug)] +pub enum OptionalValue where T: Serialize { + Value(T), + Null +} + +impl Default for OptionalValue where T: Serialize { + fn default() -> Self { + OptionalValue::Null + } +} + +impl Serialize for OptionalValue where T: Serialize { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer { + match *self { + OptionalValue::Value(ref value) => value.serialize(serializer), + OptionalValue::Null => Value::Null.serialize(serializer) + } + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use util::hash::*; + use super::*; + + #[test] + fn test_serialize_optional_value() { + let v: OptionalValue = OptionalValue::Null; + let serialized = serde_json::to_string(&v).unwrap(); + assert_eq!(serialized, r#"null"#); + + let v = OptionalValue::Value(H256::default()); + let serialized = serde_json::to_string(&v).unwrap(); + assert_eq!(serialized, r#""0x0000000000000000000000000000000000000000000000000000000000000000""#); + } +} diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 968ec2471..0e9256ada 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -16,25 +16,47 @@ use util::hash::*; use util::uint::*; -use v1::types::Bytes; +use ethcore::transaction::{LocalizedTransaction, Action}; +use v1::types::{Bytes, OptionalValue}; #[derive(Debug, Default, Serialize)] pub struct Transaction { - hash: H256, - nonce: U256, + pub hash: H256, + pub nonce: U256, #[serde(rename="blockHash")] - block_hash: H256, + pub block_hash: OptionalValue, #[serde(rename="blockNumber")] - block_number: U256, + pub block_number: OptionalValue, #[serde(rename="transactionIndex")] - transaction_index: U256, - from: Address, - to: Address, - value: U256, + pub transaction_index: OptionalValue, + pub from: Address, + pub to: OptionalValue
, + pub value: U256, #[serde(rename="gasPrice")] - gas_price: U256, - gas: U256, - input: Bytes + pub gas_price: U256, + pub gas: U256, + pub input: Bytes +} + +impl From for Transaction { + fn from(t: LocalizedTransaction) -> Transaction { + Transaction { + hash: t.hash(), + nonce: t.nonce, + block_hash: OptionalValue::Value(t.block_hash.clone()), + block_number: OptionalValue::Value(U256::from(t.block_number)), + transaction_index: OptionalValue::Value(U256::from(t.transaction_index)), + from: t.sender().unwrap(), + to: match t.action { + Action::Create => OptionalValue::Null, + Action::Call(ref address) => OptionalValue::Value(address.clone()) + }, + value: t.value, + gas_price: t.gas_price, + gas: t.gas, + input: Bytes::new(t.data.clone()) + } + } } #[cfg(test)] @@ -46,7 +68,7 @@ mod tests { fn test_transaction_serialize() { let t = Transaction::default(); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x00","transactionIndex":"0x00","from":"0x0000000000000000000000000000000000000000","to":"0x0000000000000000000000000000000000000000","value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x00"}"#); + assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x00"}"#); } } diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 7cb1b3c53..f8c08dc93 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -22,6 +22,8 @@ use ethcore::error::*; use io::SyncIo; use chain::{ChainSync}; use ethcore::receipt::Receipt; +use ethcore::transaction::LocalizedTransaction; +use ethcore::blockchain::TransactionId; pub struct TestBlockChainClient { pub blocks: RwLock>, @@ -86,6 +88,10 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } + fn transaction(&self, _id: TransactionId) -> Option { + unimplemented!(); + } + fn block_header(&self, h: &H256) -> Option { self.blocks.read().unwrap().get(h).map(|r| Rlp::new(r).at(0).as_raw().to_vec()) }