From bdf6a5660ebe3e9e22560956ffe48f8d54e2b7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 8 Aug 2016 09:59:00 +0200 Subject: [PATCH 1/8] Fixing gas conversion --- ethcore/src/evm/evm.rs | 22 ++++++++++++++++++++-- ethcore/src/evm/interpreter/mod.rs | 6 +++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index a40c03d1b..f19541d43 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -151,10 +151,14 @@ impl CostType for usize { } fn from_u256(val: U256) -> Result { - if U256::from(val.low_u64()) != val { + let res = val.low_u64() as usize; + + // validate if value fits into usize + if U256::from(res) != val { return Err(Error::OutOfGas); } - Ok(val.low_u64() as usize) + + Ok(res) } fn as_usize(&self) -> usize { @@ -191,6 +195,7 @@ pub trait Evm { #[test] +#[cfg(test)] fn should_calculate_overflow_mul_shr_without_overflow() { // given let num = 1048576; @@ -207,6 +212,7 @@ fn should_calculate_overflow_mul_shr_without_overflow() { } #[test] +#[cfg(test)] fn should_calculate_overflow_mul_shr_with_overflow() { // given let max = ::std::u64::MAX; @@ -225,3 +231,15 @@ fn should_calculate_overflow_mul_shr_with_overflow() { assert!(o1); } +#[test] +#[cfg(test)] +fn should_validate_u256_to_usize_conversion() { + // given + let v = U256::from(::std::usize::MAX) + U256::from(1); + + // when + let res = Gas::::from_u256(v); + + // then + assert!(res.is_err()); +} diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 8a3eae5b3..07aa0060b 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -515,11 +515,11 @@ impl Interpreter { Ok(InstructionResult::Ok) } - fn copy_data_to_memory(&mut self, stack: &mut Stack, data: &[u8]) { + fn copy_data_to_memory(&mut self, stack: &mut Stack, source: &[u8]) { let dest_offset = stack.pop_back(); let source_offset = stack.pop_back(); let size = stack.pop_back(); - let source_size = U256::from(data.len()); + let source_size = U256::from(source.len()); let output_end = match source_offset > source_size || size > source_size || source_offset + size > source_size { true => { @@ -531,7 +531,7 @@ impl Interpreter { for i in zero_slice.iter_mut() { *i = 0; } - data.len() + source.len() }, false => (size.low_u64() + source_offset.low_u64()) as usize }; From 10b18db833dca057d839840af02e927ee294c120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 8 Aug 2016 10:26:28 +0200 Subject: [PATCH 2/8] Validating u256->usize conversion --- Cargo.lock | 9 +++------ ethcore/src/evm/evm.rs | 2 +- ethcore/src/evm/interpreter/mod.rs | 2 +- json/src/hash.rs | 4 ++-- json/src/uint.rs | 4 ++-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46a2fd71c..baa88f12e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,16 +1549,13 @@ dependencies = [ "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" -"checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" -"checksum bitflags 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "72cd7314bd4ee024071241147222c706e80385a1605ac7d4cd2fcc339da2ae46" "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" "checksum byteorder 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e68d0b3b234a583993a53d5b0063fb5fe8713590fe733d41b98a2cee6a9c26e" "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" -"checksum chrono 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "a714b6792cb4bb07643c35d2a051d92988d4e296322a60825549dd0764bcd396" "checksum clippy 0.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "e96469b413984c78285727f94f9c626a1f2006cecdcf813b5d6893c0c85df42f" "checksum clippy_lints 0.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "f11938c4b10c556903bb1c1e717eb038658324bf7197e4cfc159a16417327345" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" @@ -1577,9 +1574,10 @@ dependencies = [ "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" "checksum hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bb0f4d00bb781e559b6e66ae4b5479df0fdf9ab15949f52fa2f1f5de16d4cc07" +"checksum hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "eb27e8a3e8f17ac43ffa41bbda9cf5ad3f9f13ef66fa4873409d4902310275f7" "checksum hyper 0.9.4 (git+https://github.com/ethcore/hyper)" = "" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" -"checksum igd 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5b93df68d6152576e9bc9f371e33e00b40738d528b3566ff41ea11d04401dc" +"checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "" @@ -1683,6 +1681,5 @@ dependencies = [ "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum ws 0.5.0 (git+https://github.com/ethcore/ws-rs.git?branch=stable)" = "" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -"checksum xml-rs 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4bac8fd82b24db2dd3b54aa7b29f336d8b5ca1830065ce3aada71bce6f661519" -"checksum xml-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f11ef7864e55d06a38755beaf03ab70139a04e619acfe94ef800b11bd79eb52c" +"checksum xml-rs 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "65e74b96bd3179209dc70a980da6df843dff09e46eee103a0376c0949257e3ef" "checksum xmltree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "472a9d37c7c53ab2391161df5b89b1f3bf76dab6ab150d7941ecbdd832282082" diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index f19541d43..2bda9c488 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -238,7 +238,7 @@ fn should_validate_u256_to_usize_conversion() { let v = U256::from(::std::usize::MAX) + U256::from(1); // when - let res = Gas::::from_u256(v); + let res = usize::from_u256(v); // then assert!(res.is_err()); diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 07aa0060b..df3ca2b4a 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -538,7 +538,7 @@ impl Interpreter { if source_offset < source_size { let output_begin = source_offset.low_u64() as usize; - self.mem.write_slice(dest_offset, &data[output_begin..output_end]); + self.mem.write_slice(dest_offset, &source[output_begin..output_end]); } } diff --git a/json/src/hash.rs b/json/src/hash.rs index ad546fcab..16d8ee9c0 100644 --- a/json/src/hash.rs +++ b/json/src/hash.rs @@ -48,10 +48,10 @@ macro_rules! impl_hash { 0 => $inner::from(0), 2 if value == "0x" => $inner::from(0), _ if value.starts_with("0x") => try!($inner::from_str(&value[2..]).map_err(|_| { - Error::custom(format!("Invalid hex value {}.", value).as_ref()) + Error::custom(format!("Invalid hex value {}.", value).as_str()) })), _ => try!($inner::from_str(value).map_err(|_| { - Error::custom(format!("Invalid hex value {}.", value).as_ref()) + Error::custom(format!("Invalid hex value {}.", value).as_str()) })) }; diff --git a/json/src/uint.rs b/json/src/uint.rs index e77001461..bcab142f6 100644 --- a/json/src/uint.rs +++ b/json/src/uint.rs @@ -70,10 +70,10 @@ impl Visitor for UintVisitor { 0 => U256::from(0), 2 if value.starts_with("0x") => U256::from(0), _ if value.starts_with("0x") => try!(U256::from_str(&value[2..]).map_err(|_| { - Error::custom(format!("Invalid hex value {}.", value).as_ref()) + Error::custom(format!("Invalid hex value {}.", value).as_str()) })), _ => try!(U256::from_dec_str(value).map_err(|_| { - Error::custom(format!("Invalid decimal value {}.", value).as_ref()) + Error::custom(format!("Invalid decimal value {}.", value).as_str()) })) }; From 35451e31d4a6c22b896b314606d58e7468fe06e8 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 8 Aug 2016 13:47:00 +0200 Subject: [PATCH 3/8] Update cache usage on commiting block info (#1871) --- ethcore/src/blockchain/blockchain.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 3fa728686..a374c0bf6 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -775,12 +775,20 @@ impl BlockChain { /// Applt pending insertion updates pub fn commit(&self) { - let mut best_block = self.best_block.write(); - let mut write_hashes = self.block_hashes.write(); - let mut write_txs = self.transaction_addresses.write(); let mut pending_best_block = self.pending_best_block.write(); let mut pending_write_hashes = self.pending_block_hashes.write(); let mut pending_write_txs = self.pending_transaction_addresses.write(); + + for n in pending_write_hashes.keys() { + self.note_used(CacheID::BlockHashes(*n)); + } + for hash in pending_write_txs.keys() { + self.note_used(CacheID::TransactionAddresses(hash.clone())); + } + + let mut best_block = self.best_block.write(); + let mut write_hashes = self.block_hashes.write(); + let mut write_txs = self.transaction_addresses.write(); // update best block if let Some(block) = pending_best_block.take() { *best_block = block; From 676244722974b6e742f629e669db14e19f024b48 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 8 Aug 2016 16:56:35 +0200 Subject: [PATCH 4/8] Use UntrustedRlp for block verification (#1872) --- ethcore/src/verification/verification.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index ed094a1d2..851a702a9 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -40,7 +40,8 @@ pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Res 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::
()) { + for u in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { + let u = try!(u); try!(verify_header(&u, engine)); try!(engine.verify_block_basic(&u, None)); } @@ -58,8 +59,8 @@ pub fn verify_block_basic(header: &Header, bytes: &[u8], engine: &Engine) -> Res /// Returns a `PreverifiedBlock` structure populated with transactions pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine) -> Result { try!(engine.verify_block_unordered(&header, Some(&bytes))); - for u in Rlp::new(&bytes).at(2).iter().map(|rlp| rlp.as_val::
()) { - try!(engine.verify_block_unordered(&u, None)); + for u in try!(UntrustedRlp::new(&bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { + try!(engine.verify_block_unordered(&try!(u), None)); } // Verify transactions. let mut transactions = Vec::new(); @@ -84,7 +85,7 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & try!(verify_parent(&header, &parent)); try!(engine.verify_block_family(&header, &parent, Some(bytes))); - let num_uncles = Rlp::new(bytes).at(2).item_count(); + let num_uncles = try!(UntrustedRlp::new(bytes).at(2)).item_count(); if num_uncles != 0 { if num_uncles > engine.maximum_uncle_count() { return Err(From::from(BlockError::TooManyUncles(OutOfBounds { min: None, max: Some(engine.maximum_uncle_count()), found: num_uncles }))); @@ -106,7 +107,8 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: & } } - for uncle in Rlp::new(bytes).at(2).iter().map(|rlp| rlp.as_val::
()) { + for uncle in try!(UntrustedRlp::new(bytes).at(2)).iter().map(|rlp| rlp.as_val::
()) { + let uncle = try!(uncle); if excluded.contains(&uncle.hash()) { return Err(From::from(BlockError::UncleInChain(uncle.hash()))) } @@ -210,13 +212,13 @@ fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> { /// Verify block data against header: transactions root and uncles hash. fn verify_block_integrity(block: &[u8], transactions_root: &H256, uncles_hash: &H256) -> Result<(), Error> { - let block = Rlp::new(block); - let tx = block.at(1); + let block = UntrustedRlp::new(block); + let tx = try!(block.at(1)); let expected_root = &ordered_trie_root(tx.iter().map(|r| r.as_raw().to_vec()).collect()); //TODO: get rid of vectors here if expected_root != transactions_root { return Err(From::from(BlockError::InvalidTransactionsRoot(Mismatch { expected: expected_root.clone(), found: transactions_root.clone() }))) } - let expected_uncles = &block.at(2).as_raw().sha3(); + let expected_uncles = &try!(block.at(2)).as_raw().sha3(); if expected_uncles != uncles_hash { return Err(From::from(BlockError::InvalidUnclesHash(Mismatch { expected: expected_uncles.clone(), found: uncles_hash.clone() }))) } From 2e5a6ea1ffbc8e02eeb0c2569a8d421ec0eb0fc3 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 8 Aug 2016 17:18:10 +0200 Subject: [PATCH 5/8] Send new block hashes to all peers (#1875) --- sync/src/chain.rs | 60 +++++++++++++++++++++++------------------ sync/src/tests/chain.rs | 9 ++++--- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index b7212a116..71d27127b 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1376,27 +1376,23 @@ impl ChainSync { .collect::>() } - fn select_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo) -> Vec<(PeerId, BlockNumber)> { + fn select_random_lagging_peers(&mut self, peers: &[(PeerId, BlockNumber)]) -> Vec<(PeerId, BlockNumber)> { use rand::Rng; - let mut lagging_peers = self.get_lagging_peers(chain_info, io); // take sqrt(x) peers + let mut peers = peers.to_vec(); let mut count = (self.peers.len() as f64).powf(0.5).round() as usize; count = min(count, MAX_PEERS_PROPAGATION); count = max(count, MIN_PEERS_PROPAGATION); - ::rand::thread_rng().shuffle(&mut lagging_peers); - lagging_peers.into_iter().take(count).collect::>() + ::rand::thread_rng().shuffle(&mut peers); + peers.truncate(count); + peers } /// propagates latest block to lagging peers - fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256]) -> usize { - let lucky_peers: Vec<_> = if sealed.is_empty() { - self.select_lagging_peers(chain_info, io).iter().map(|&(id, _)| id).collect() - } else { - self.peers.keys().cloned().collect() - }; - trace!(target: "sync", "Sending NewBlocks to {:?}", lucky_peers); + fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[(PeerId, BlockNumber)]) -> usize { + trace!(target: "sync", "Sending NewBlocks to {:?}", peers); let mut sent = 0; - for peer_id in lucky_peers { + for &(peer_id, _) in peers { if sealed.is_empty() { let rlp = ChainSync::create_latest_block_rlp(io.chain()); self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); @@ -1414,12 +1410,11 @@ impl ChainSync { } /// propagates new known hashes to all peers - fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo) -> usize { - let lucky_peers = self.select_lagging_peers(chain_info, io); - trace!(target: "sync", "Sending NewHashes to {:?}", lucky_peers); + fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[(PeerId, BlockNumber)]) -> usize { + trace!(target: "sync", "Sending NewHashes to {:?}", peers); let mut sent = 0; let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())).unwrap()).parent_hash(); - for (peer_id, peer_number) in lucky_peers { + for &(peer_id, peer_number) in peers { let peer_best = if chain_info.best_block_number - peer_number > MAX_PEER_LAG_PROPAGATION as BlockNumber { // If we think peer is too far behind just send one latest hash last_parent.clone() @@ -1485,11 +1480,19 @@ impl ChainSync { fn propagate_latest_blocks(&mut self, io: &mut SyncIo, sealed: &[H256]) { let chain_info = io.chain().chain_info(); if (((chain_info.best_block_number as i64) - (self.last_sent_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION { - let hashes = self.propagate_new_hashes(&chain_info, io); - let blocks = self.propagate_blocks(&chain_info, io, sealed); - if blocks != 0 || hashes != 0 { - trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes); - } + let mut peers = self.get_lagging_peers(&chain_info, io); + if sealed.is_empty() { + let hashes = self.propagate_new_hashes(&chain_info, io, &peers); + peers = self.select_random_lagging_peers(&peers); + let blocks = self.propagate_blocks(&chain_info, io, sealed, &peers); + if blocks != 0 || hashes != 0 { + trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes); + } + } else { + self.propagate_blocks(&chain_info, io, sealed, &peers); + self.propagate_new_hashes(&chain_info, io, &peers); + trace!(target: "sync", "Sent sealed block to all peers"); + }; } self.propagate_new_transactions(io); self.last_sent_block_number = chain_info.best_block_number; @@ -1757,7 +1760,8 @@ mod tests { let chain_info = client.chain_info(); let mut io = TestIo::new(&mut client, &mut queue, None); - let peer_count = sync.propagate_new_hashes(&chain_info, &mut io); + let peers = sync.get_lagging_peers(&chain_info, &mut io); + let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers); // 1 message should be send assert_eq!(1, io.queue.len()); @@ -1775,7 +1779,8 @@ mod tests { let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let mut io = TestIo::new(&mut client, &mut queue, None); - let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[]); + let peers = sync.get_lagging_peers(&chain_info, &mut io); + let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers); // 1 message should be send assert_eq!(1, io.queue.len()); @@ -1794,7 +1799,8 @@ mod tests { let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let mut io = TestIo::new(&mut client, &mut queue, None); - let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()]); + let peers = sync.get_lagging_peers(&chain_info, &mut io); + let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers); // 1 message should be send assert_eq!(1, io.queue.len()); @@ -1900,7 +1906,8 @@ mod tests { let chain_info = client.chain_info(); let mut io = TestIo::new(&mut client, &mut queue, None); - sync.propagate_new_hashes(&chain_info, &mut io); + let peers = sync.get_lagging_peers(&chain_info, &mut io); + sync.propagate_new_hashes(&chain_info, &mut io, &peers); let data = &io.queue[0].data.clone(); let result = sync.on_peer_new_hashes(&mut io, 0, &UntrustedRlp::new(data)); @@ -1918,7 +1925,8 @@ mod tests { let chain_info = client.chain_info(); let mut io = TestIo::new(&mut client, &mut queue, None); - sync.propagate_blocks(&chain_info, &mut io, &[]); + let peers = sync.get_lagging_peers(&chain_info, &mut io); + sync.propagate_blocks(&chain_info, &mut io, &[], &peers); let data = &io.queue[0].data.clone(); let result = sync.on_peer_new_block(&mut io, 0, &UntrustedRlp::new(data)); diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 2a84b0f99..94dcc2a9d 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -161,11 +161,11 @@ fn propagate_hashes() { net.trigger_chain_new_blocks(0); //first event just sets the marker net.trigger_chain_new_blocks(0); - // 5 peers to sync - assert_eq!(5, net.peer(0).queue.len()); + // 5 peers with NewHahses, 4 with blocks + assert_eq!(9, net.peer(0).queue.len()); let mut hashes = 0; let mut blocks = 0; - for i in 0..5 { + for i in 0..net.peer(0).queue.len() { if net.peer(0).queue[i].packet_id == 0x1 { hashes += 1; } @@ -173,7 +173,8 @@ fn propagate_hashes() { blocks += 1; } } - assert!(blocks + hashes == 5); + assert_eq!(blocks, 4); + assert_eq!(hashes, 5); } #[test] From 7ed4bded52f276f72b4e4f9437797dba6068cea2 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 8 Aug 2016 17:18:29 +0200 Subject: [PATCH 6/8] Reduce max open files (#1876) --- util/src/kvdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/kvdb.rs b/util/src/kvdb.rs index 469a124bb..f88e74034 100644 --- a/util/src/kvdb.rs +++ b/util/src/kvdb.rs @@ -172,7 +172,7 @@ impl Default for DatabaseConfig { fn default() -> DatabaseConfig { DatabaseConfig { cache_size: None, - max_open_files: 1024, + max_open_files: 512, compaction: CompactionProfile::default(), columns: None, wal: true, From 59b0f8c7a30380307094197fb9ea1acd1c8c639d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Mon, 8 Aug 2016 17:25:15 +0200 Subject: [PATCH 7/8] RPC errors & logs (#1845) * Refactoring errors in RPC * Updating jsonrpc-core * Fixing code_at * Avoid mentioning obvious segments in proof [ci:skip] --- Cargo.lock | 16 +- dapps/Cargo.toml | 2 +- ethcore/src/client/client.rs | 4 +- ethcore/src/client/test_client.rs | 7 +- ethcore/src/client/traits.rs | 10 +- ethcore/src/miner/miner.rs | 2 +- rpc/Cargo.toml | 2 +- rpc/src/v1/helpers/dispatch.rs | 96 ++++++++++ rpc/src/v1/helpers/errors.rs | 188 ++++++++++++++++++++ rpc/src/v1/helpers/mod.rs | 4 + rpc/src/v1/helpers/params.rs | 53 ++++++ rpc/src/v1/impls/eth.rs | 260 ++++++++++++---------------- rpc/src/v1/impls/eth_filter.rs | 29 ++-- rpc/src/v1/impls/eth_signing.rs | 14 +- rpc/src/v1/impls/ethcore.rs | 69 ++++---- rpc/src/v1/impls/ethcore_set.rs | 18 +- rpc/src/v1/impls/mod.rs | 211 +--------------------- rpc/src/v1/impls/net.rs | 10 +- rpc/src/v1/impls/personal.rs | 34 ++-- rpc/src/v1/impls/personal_signer.rs | 10 +- rpc/src/v1/impls/rpc.rs | 7 +- rpc/src/v1/impls/traces.rs | 6 +- rpc/src/v1/impls/web3.rs | 7 +- rpc/src/v1/mod.rs | 3 +- signer/Cargo.toml | 2 +- 25 files changed, 602 insertions(+), 462 deletions(-) create mode 100644 rpc/src/v1/helpers/dispatch.rs create mode 100644 rpc/src/v1/helpers/errors.rs create mode 100644 rpc/src/v1/helpers/params.rs diff --git a/Cargo.lock b/Cargo.lock index baa88f12e..99d301023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,7 +274,7 @@ dependencies = [ "ethcore-rpc 1.3.0", "ethcore-util 1.3.0", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -414,7 +414,7 @@ dependencies = [ "ethjson 0.1.0", "ethsync 1.3.0", "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -434,7 +434,7 @@ dependencies = [ "ethcore-io 1.3.0", "ethcore-rpc 1.3.0", "ethcore-util 1.3.0", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-signer 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -682,7 +682,7 @@ source = "git+https://github.com/ethcore/json-ipc-server.git#56b6307130710ebc73c dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -692,13 +692,13 @@ dependencies = [ [[package]] name = "jsonrpc-core" -version = "2.0.7" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -707,7 +707,7 @@ version = "6.1.0" source = "git+https://github.com/ethcore/jsonrpc-http-server.git#4e3f93eb79125e91a46e04d77c25ff8885498b86" dependencies = [ "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1581,7 +1581,7 @@ dependencies = [ "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "" -"checksum jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "91755680900913f73576065c85359ee793ac3883bc461dbca90fc4a603be84cc" +"checksum jsonrpc-core 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec4477e4e8218da23caa5dd31f4eb39999aa0ea9035660617eccfb19a23bf5ad" "checksum jsonrpc-http-server 6.1.0 (git+https://github.com/ethcore/jsonrpc-http-server.git)" = "" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 1586677ac..01aeb8826 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -10,7 +10,7 @@ build = "build.rs" [dependencies] log = "0.3" -jsonrpc-core = "2.0" +jsonrpc-core = "2.1" jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } unicase = "1.3" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ca23fb5a1..e383ce193 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -784,8 +784,8 @@ impl BlockChainClient for Client { Self::block_hash(&self.chain, id) } - fn code(&self, address: &Address) -> Option { - self.state().code(address) + fn code(&self, address: &Address, id: BlockID) -> Option> { + self.state_at(id).map(|s| s.code(address)) } fn balance(&self, address: &Address, id: BlockID) -> Option { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 113974dee..eedbe4a5b 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -319,8 +319,11 @@ impl BlockChainClient for TestBlockChainClient { self.nonce(address, BlockID::Latest).unwrap() } - fn code(&self, address: &Address) -> Option { - self.code.read().get(address).cloned() + fn code(&self, address: &Address, id: BlockID) -> Option> { + match id { + BlockID::Latest => Some(self.code.read().get(address).cloned()), + _ => None, + } } fn balance(&self, address: &Address, id: BlockID) -> Option { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index ca4b8e6b1..f91747e7b 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -81,8 +81,14 @@ pub trait BlockChainClient : Sync + Send { /// Get block hash. fn block_hash(&self, id: BlockID) -> Option; - /// Get address code. - fn code(&self, address: &Address) -> Option; + /// Get address code at given block's state. + fn code(&self, address: &Address, id: BlockID) -> Option>; + + /// Get address code at the latest block's state. + fn latest_code(&self, address: &Address) -> Option { + self.code(address, BlockID::Latest) + .expect("code will return Some if given BlockID::Latest; qed") + } /// Get address balance at the given block's state. /// diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 421532278..3328565eb 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -528,7 +528,7 @@ impl MinerService for Miner { fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else(|| chain.code(address), |b| b.block().fields().state.code(address)) + sealing_work.queue.peek_last_ref().map_or_else(|| chain.latest_code(address), |b| b.block().fields().state.code(address)) } fn set_author(&self, author: Address) { diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index b02241046..441ea723c 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -12,7 +12,7 @@ build = "build.rs" log = "0.3" serde = "0.7.0" serde_json = "0.7.0" -jsonrpc-core = "2.0" +jsonrpc-core = "2.1" jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } ethcore-io = { path = "../util/io" } ethcore-util = { path = "../util" } diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs new file mode 100644 index 000000000..f709e59ab --- /dev/null +++ b/rpc/src/v1/helpers/dispatch.rs @@ -0,0 +1,96 @@ +// 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 util::numbers::*; +use util::rlp::encode; +use util::bytes::ToPretty; +use ethcore::miner::MinerService; +use ethcore::client::MiningBlockChainClient; +use ethcore::transaction::{Action, SignedTransaction, Transaction}; +use ethcore::account_provider::AccountProvider; +use jsonrpc_core::{Error, Value, to_value}; +use v1::helpers::TransactionRequest; +use v1::types::{H256 as RpcH256, H520 as RpcH520}; +use v1::helpers::errors; + +fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { + Transaction { + nonce: request.nonce + .or_else(|| miner + .last_nonce(&request.from) + .map(|nonce| nonce + U256::one())) + .unwrap_or_else(|| client.latest_nonce(&request.from)), + + action: request.to.map_or(Action::Create, Action::Call), + gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), + gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), + value: request.value.unwrap_or_else(U256::zero), + data: request.data.map_or_else(Vec::new, |b| b.to_vec()), + } +} + +pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result + where C: MiningBlockChainClient, M: MinerService { + let hash = RpcH256::from(signed_transaction.hash()); + + let import = miner.import_own_transaction(client, signed_transaction); + + import + .map_err(errors::from_transaction_error) + .and_then(|_| to_value(&hash)) +} + +pub fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result { + accounts.sign_with_password(address, pass, hash) + .map_err(errors::from_password_error) + .and_then(|hash| to_value(&RpcH520::from(hash))) +} + +pub fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result + where C: MiningBlockChainClient, M: MinerService { + + let address = request.from; + let signed_transaction = { + let t = prepare_transaction(client, miner, request); + let hash = t.hash(); + let signature = try!(account_provider.sign_with_password(address, password, hash).map_err(errors::from_password_error)); + t.with_signature(signature) + }; + + trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); + dispatch_transaction(&*client, &*miner, signed_transaction) +} + +pub fn sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address) -> Result + where C: MiningBlockChainClient, M: MinerService { + + let signed_transaction = { + let t = prepare_transaction(client, miner, request); + let hash = t.hash(); + let signature = try!(account_provider.sign(address, hash).map_err(errors::from_signing_error)); + t.with_signature(signature) + }; + + trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); + dispatch_transaction(&*client, &*miner, signed_transaction) +} + +pub fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { + client + .gas_price_statistics(100, 8) + .map(|x| x[4]) + .unwrap_or_else(|_| miner.sensible_gas_price()) +} diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs new file mode 100644 index 000000000..bb62a80e5 --- /dev/null +++ b/rpc/src/v1/helpers/errors.rs @@ -0,0 +1,188 @@ +// 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 . + +//! RPC Error codes and error objects + +macro_rules! rpc_unimplemented { + () => (Err(::v1::helpers::errors::unimplemented())) +} + +use std::fmt; +use ethcore::error::Error as EthcoreError; +use ethcore::account_provider::{Error as AccountError}; +use jsonrpc_core::{Error, ErrorCode, Value}; + +mod codes { + // NOTE [ToDr] Codes from [-32099, -32000] + pub const UNSUPPORTED_REQUEST: i64 = -32000; + pub const NO_WORK: i64 = -32001; + pub const NO_AUTHOR: i64 = -32002; + pub const UNKNOWN_ERROR: i64 = -32009; + pub const TRANSACTION_ERROR: i64 = -32010; + pub const ACCOUNT_LOCKED: i64 = -32020; + pub const PASSWORD_INVALID: i64 = -32021; + pub const ACCOUNT_ERROR: i64 = -32023; + pub const SIGNER_DISABLED: i64 = -32030; + pub const REQUEST_REJECTED: i64 = -32040; + pub const REQUEST_NOT_FOUND: i64 = -32041; + pub const COMPILATION_ERROR: i64 = -32050; +} + +pub fn unimplemented() -> Error { + Error { + code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), + message: "This request is not implemented yet. Please create an issue on Github repo.".into(), + data: None + } +} + +pub fn request_not_found() -> Error { + Error { + code: ErrorCode::ServerError(codes::REQUEST_NOT_FOUND), + message: "Request not found.".into(), + data: None, + } +} + +pub fn request_rejected() -> Error { + Error { + code: ErrorCode::ServerError(codes::REQUEST_REJECTED), + message: "Request has been rejected.".into(), + data: None, + } +} + + +pub fn account(error: &str, details: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::ACCOUNT_ERROR), + message: error.into(), + data: Some(Value::String(format!("{:?}", details))), + } +} + +pub fn compilation(error: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::COMPILATION_ERROR), + message: "Error while compiling code.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + +pub fn internal(error: &str, data: T) -> Error { + Error { + code: ErrorCode::InternalError, + message: format!("Internal error occurred: {}", error), + data: Some(Value::String(format!("{:?}", data))), + } +} + +pub fn invalid_params(param: &str, details: T) -> Error { + Error { + code: ErrorCode::InvalidParams, + message: format!("Couldn't parse parameters: {}", param), + data: Some(Value::String(format!("{:?}", details))), + } +} + +pub fn state_pruned() -> Error { + Error { + code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), + message: "This request is not supported because your node is running with state pruning. Run with --pruning=archive.".into(), + data: None + } +} + +pub fn no_work() -> Error { + Error { + code: ErrorCode::ServerError(codes::NO_WORK), + message: "Still syncing.".into(), + data: None + } +} + +pub fn no_author() -> Error { + Error { + code: ErrorCode::ServerError(codes::NO_AUTHOR), + message: "Author not configured. Run Parity with --author to configure.".into(), + data: None + } +} + + +pub fn signer_disabled() -> Error { + Error { + code: ErrorCode::ServerError(codes::SIGNER_DISABLED), + message: "Trusted Signer is disabled. This API is not available.".into(), + data: None + } +} + +pub fn from_signing_error(error: AccountError) -> Error { + Error { + code: ErrorCode::ServerError(codes::ACCOUNT_LOCKED), + message: "Your account is locked. Unlock the account via CLI, personal_unlockAccount or use Trusted Signer.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + +pub fn from_password_error(error: AccountError) -> Error { + Error { + code: ErrorCode::ServerError(codes::PASSWORD_INVALID), + message: "Account password is invalid or account does not exist.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + +pub fn from_transaction_error(error: EthcoreError) -> Error { + use ethcore::error::TransactionError::*; + + if let EthcoreError::Transaction(e) = error { + let msg = match e { + AlreadyImported => "Transaction with the same hash was already imported.".into(), + Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), + TooCheapToReplace => { + "Transaction fee is too low. There is another transaction with same nonce in the queue. Try increasing the fee or incrementing the nonce.".into() + }, + LimitReached => { + "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() + }, + InsufficientGasPrice { minimal, got } => { + format!("Transaction fee is too low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", minimal, got) + }, + InsufficientBalance { balance, cost } => { + format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) + }, + GasLimitExceeded { limit, got } => { + format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) + }, + InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), + }; + Error { + code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), + message: msg, + data: None, + } + } else { + Error { + code: ErrorCode::ServerError(codes::UNKNOWN_ERROR), + message: "Unknown error when sending transaction.".into(), + data: Some(Value::String(format!("{:?}", error))), + } + } +} + + diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 315bb7a08..d71eaac41 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +#[macro_use] +pub mod errors; +pub mod dispatch; +pub mod params; mod poll_manager; mod poll_filter; mod requests; diff --git a/rpc/src/v1/helpers/params.rs b/rpc/src/v1/helpers/params.rs new file mode 100644 index 000000000..c38529e4e --- /dev/null +++ b/rpc/src/v1/helpers/params.rs @@ -0,0 +1,53 @@ +// 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 . + +//! Parameters parsing helpers + +use serde; +use jsonrpc_core::{Error, Params, from_params}; +use v1::types::BlockNumber; +use v1::helpers::errors; + +pub fn expect_no_params(params: Params) -> Result<(), Error> { + match params { + Params::None => Ok(()), + p => Err(errors::invalid_params("No parameters were expected", p)), + } +} + +fn params_len(params: &Params) -> usize { + match params { + &Params::Array(ref vec) => vec.len(), + _ => 0, + } +} + +/// Deserialize request parameters with optional second parameter `BlockNumber` defaulting to `BlockNumber::Latest`. +pub fn from_params_default_second(params: Params) -> Result<(F, BlockNumber, ), Error> where F: serde::de::Deserialize { + match params_len(¶ms) { + 1 => from_params::<(F, )>(params).map(|(f,)| (f, BlockNumber::Latest)), + _ => from_params::<(F, BlockNumber)>(params), + } +} + +/// Deserialize request parameters with optional third parameter `BlockNumber` defaulting to `BlockNumber::Latest`. +pub fn from_params_default_third(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize { + match params_len(¶ms) { + 2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)), + _ => from_params::<(F1, F2, BlockNumber)>(params) + } +} + diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 0d2b6164a..e1fecd7ed 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -43,8 +43,9 @@ use ethcore::filter::Filter as EthcoreFilter; use self::ethash::SeedHashCompute; use v1::traits::Eth; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, Index, Filter, Log, Receipt, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256}; -use v1::helpers::CallRequest as CRequest; -use v1::impls::{default_gas_price, dispatch_transaction, error_codes, from_params_default_second, from_params_default_third}; +use v1::helpers::{CallRequest as CRequest, errors}; +use v1::helpers::dispatch::{default_gas_price, dispatch_transaction}; +use v1::helpers::params::{expect_no_params, from_params_default_second, from_params_default_third}; /// Eth RPC options pub struct EthClientOptions { @@ -214,30 +215,6 @@ pub fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: M const MAX_QUEUE_SIZE_TO_MINE_ON: usize = 4; // because uncles go back 6. -fn make_unsupported_err() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::UNSUPPORTED_REQUEST_CODE), - message: "Unsupported request.".into(), - data: None - } -} - -fn no_work_err() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::NO_WORK_CODE), - message: "Still syncing.".into(), - data: None - } -} - -fn no_author_err() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::NO_AUTHOR_CODE), - message: "Author not configured. Run parity with --author to configure.".into(), - data: None - } -} - impl EthClient where C: MiningBlockChainClient + 'static, S: SyncProvider + 'static, @@ -265,94 +242,80 @@ impl Eth for EthClient where fn protocol_version(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => Ok(Value::String(format!("{}", take_weak!(self.sync).status().protocol_version).to_owned())), - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + Ok(Value::String(format!("{}", take_weak!(self.sync).status().protocol_version).to_owned())) } fn syncing(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let status = take_weak!(self.sync).status(); - let res = match status.state { - SyncState::Idle => SyncStatus::None, - SyncState::Waiting | SyncState::Blocks | SyncState::NewBlocks | SyncState::ChainHead => { - let current_block = U256::from(take_weak!(self.client).chain_info().best_block_number); - let highest_block = U256::from(status.highest_block_number.unwrap_or(status.start_block_number)); + try!(expect_no_params(params)); - if highest_block > current_block + U256::from(6) { - let info = SyncInfo { - starting_block: status.start_block_number.into(), - current_block: current_block.into(), - highest_block: highest_block.into(), - }; - SyncStatus::Info(info) - } else { - SyncStatus::None - } - } - }; - to_value(&res) + let status = take_weak!(self.sync).status(); + let res = match status.state { + SyncState::Idle => SyncStatus::None, + SyncState::Waiting | SyncState::Blocks | SyncState::NewBlocks | SyncState::ChainHead => { + let current_block = U256::from(take_weak!(self.client).chain_info().best_block_number); + let highest_block = U256::from(status.highest_block_number.unwrap_or(status.start_block_number)); + + if highest_block > current_block + U256::from(6) { + let info = SyncInfo { + starting_block: status.start_block_number.into(), + current_block: current_block.into(), + highest_block: highest_block.into(), + }; + SyncStatus::Info(info) + } else { + SyncStatus::None + } } - _ => Err(Error::invalid_params()), - } + }; + to_value(&res) } fn author(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => to_value(&RpcH160::from(take_weak!(self.miner).author())), - _ => Err(Error::invalid_params()), - } + try!(expect_no_params(params)); + + to_value(&RpcH160::from(take_weak!(self.miner).author())) } fn is_mining(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => to_value(&(take_weak!(self.miner).is_sealing())), - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + to_value(&(take_weak!(self.miner).is_sealing())) } fn hashrate(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => to_value(&RpcU256::from(self.external_miner.hashrate())), - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + to_value(&RpcU256::from(self.external_miner.hashrate())) } fn gas_price(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); - to_value(&RpcU256::from(default_gas_price(&*client, &*miner))) - } - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); + to_value(&RpcU256::from(default_gas_price(&*client, &*miner))) } fn accounts(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let store = take_weak!(self.accounts); - let accounts = try!(store.accounts().map_err(|_| Error::internal_error())); - to_value(&accounts.into_iter().map(Into::into).collect::>()) - }, - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + let store = take_weak!(self.accounts); + let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e))); + to_value(&accounts.into_iter().map(Into::into).collect::>()) } fn block_number(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => to_value(&RpcU256::from(take_weak!(self.client).chain_info().best_block_number)), - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + to_value(&RpcU256::from(take_weak!(self.client).chain_info().best_block_number)) } fn balance(&self, params: Params) -> Result { @@ -362,7 +325,10 @@ impl Eth for EthClient where let address: Address = RpcH160::into(address); match block_number { BlockNumber::Pending => to_value(&RpcU256::from(take_weak!(self.miner).balance(take_weak!(self.client).deref(), &address))), - id => to_value(&RpcU256::from(try!(take_weak!(self.client).balance(&address, id.into()).ok_or_else(make_unsupported_err)))), + id => match take_weak!(self.client).balance(&address, id.into()) { + Some(balance) => to_value(&RpcU256::from(balance)), + None => Err(errors::state_pruned()), + } } }) } @@ -377,7 +343,7 @@ impl Eth for EthClient where BlockNumber::Pending => to_value(&RpcU256::from(take_weak!(self.miner).storage_at(&*take_weak!(self.client), &address, &H256::from(position)))), id => match take_weak!(self.client).storage_at(&address, &H256::from(position), id.into()) { Some(s) => to_value(&RpcH256::from(s)), - None => Err(make_unsupported_err()), // None is only returned on unsupported requests. + None => Err(errors::state_pruned()), } } }) @@ -391,7 +357,10 @@ impl Eth for EthClient where let address: Address = RpcH160::into(address); match block_number { BlockNumber::Pending => to_value(&RpcU256::from(take_weak!(self.miner).nonce(take_weak!(self.client).deref(), &address))), - id => to_value(&take_weak!(self.client).nonce(&address, id.into()).map(RpcU256::from)), + id => match take_weak!(self.client).nonce(&address, id.into()) { + Some(nonce) => to_value(&RpcU256::from(nonce)), + None => Err(errors::state_pruned()), + } } }) } @@ -441,8 +410,10 @@ impl Eth for EthClient where let address: Address = RpcH160::into(address); match block_number { BlockNumber::Pending => to_value(&take_weak!(self.miner).code(take_weak!(self.client).deref(), &address).map_or_else(Bytes::default, Bytes::new)), - BlockNumber::Latest => to_value(&take_weak!(self.client).code(&address).map_or_else(Bytes::default, Bytes::new)), - _ => Err(Error::invalid_params()), + _ => match take_weak!(self.client).code(&address, block_number.into()) { + Some(code) => to_value(&code.map_or_else(Bytes::default, Bytes::new)), + None => Err(errors::state_pruned()), + }, } }) } @@ -515,16 +486,13 @@ impl Eth for EthClient where fn compilers(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let mut compilers = vec![]; - if Command::new(SOLC).output().is_ok() { - compilers.push("solidity".to_owned()) - } - to_value(&compilers) - } - _ => Err(Error::invalid_params()) + try!(expect_no_params(params)); + + let mut compilers = vec![]; + if Command::new(SOLC).output().is_ok() { + compilers.push("solidity".to_owned()) } + to_value(&compilers) } fn logs(&self, params: Params) -> Result { @@ -549,45 +517,42 @@ impl Eth for EthClient where fn work(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let client = take_weak!(self.client); - // check if we're still syncing and return empty strings in that case - { - //TODO: check if initial sync is complete here - //let sync = take_weak!(self.sync); - if /*sync.status().state != SyncState::Idle ||*/ client.queue_info().total_queue_size() > MAX_QUEUE_SIZE_TO_MINE_ON { - trace!(target: "miner", "Syncing. Cannot give any work."); - return Err(no_work_err()); - } + try!(expect_no_params(params)); - // Otherwise spin until our submitted block has been included. - let timeout = Instant::now() + Duration::from_millis(1000); - while Instant::now() < timeout && client.queue_info().total_queue_size() > 0 { - thread::sleep(Duration::from_millis(1)); - } - } + let client = take_weak!(self.client); + // check if we're still syncing and return empty strings in that case + { + //TODO: check if initial sync is complete here + //let sync = take_weak!(self.sync); + if /*sync.status().state != SyncState::Idle ||*/ client.queue_info().total_queue_size() > MAX_QUEUE_SIZE_TO_MINE_ON { + trace!(target: "miner", "Syncing. Cannot give any work."); + return Err(errors::no_work()); + } - let miner = take_weak!(self.miner); - if miner.author().is_zero() { - warn!(target: "miner", "Cannot give work package - no author is configured. Use --author to configure!"); - return Err(no_author_err()) - } - miner.map_sealing_work(client.deref(), |b| { - let pow_hash = b.hash(); - let target = Ethash::difficulty_to_boundary(b.block().header().difficulty()); - let seed_hash = self.seed_compute.lock().get_seedhash(b.block().header().number()); - - if self.options.send_block_number_in_get_work { - let block_number = RpcU256::from(b.block().header().number()); - to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number)) - } else { - to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target))) - } - }).unwrap_or(Err(Error::internal_error())) // no work found. - }, - _ => Err(Error::invalid_params()) + // Otherwise spin until our submitted block has been included. + let timeout = Instant::now() + Duration::from_millis(1000); + while Instant::now() < timeout && client.queue_info().total_queue_size() > 0 { + thread::sleep(Duration::from_millis(1)); + } } + + let miner = take_weak!(self.miner); + if miner.author().is_zero() { + warn!(target: "miner", "Cannot give work package - no author is configured. Use --author to configure!"); + return Err(errors::no_author()) + } + miner.map_sealing_work(client.deref(), |b| { + let pow_hash = b.hash(); + let target = Ethash::difficulty_to_boundary(b.block().header().difficulty()); + let seed_hash = self.seed_compute.lock().get_seedhash(b.block().header().number()); + + if self.options.send_block_number_in_get_work { + let block_number = RpcU256::from(b.block().header().number()); + to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number)) + } else { + to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target))) + } + }).unwrap_or(Err(Error::internal_error())) // no work found. } fn submit_work(&self, params: Params) -> Result { @@ -627,7 +592,6 @@ impl Eth for EthClient where fn call(&self, params: Params) -> Result { try!(self.active()); - trace!(target: "jsonrpc", "call: {:?}", params); from_params_default_second(params) .and_then(|(request, block_number,)| { let request = CallRequest::into(request); @@ -675,17 +639,23 @@ impl Eth for EthClient where .stdout(Stdio::piped()) .stderr(Stdio::null()) .spawn(); - if let Ok(mut child) = maybe_child { - if let Ok(_) = child.stdin.as_mut().expect("we called child.stdin(Stdio::piped()) before spawn; qed").write_all(code.as_bytes()) { - if let Ok(output) = child.wait_with_output() { - let s = String::from_utf8_lossy(&output.stdout); - if let Some(hex) = s.lines().skip_while(|ref l| !l.contains("Binary")).skip(1).next() { - return to_value(&Bytes::new(hex.from_hex().unwrap_or(vec![]))); - } + + maybe_child + .map_err(errors::compilation) + .and_then(|mut child| { + try!(child.stdin.as_mut() + .expect("we called child.stdin(Stdio::piped()) before spawn; qed") + .write_all(code.as_bytes()) + .map_err(errors::compilation)); + let output = try!(child.wait_with_output().map_err(errors::compilation)); + + let s = String::from_utf8_lossy(&output.stdout); + if let Some(hex) = s.lines().skip_while(|ref l| !l.contains("Binary")).skip(1).next() { + to_value(&Bytes::new(hex.from_hex().unwrap_or(vec![]))) + } else { + Err(errors::compilation("Unexpected output.")) } - } - } - Err(Error::invalid_params()) + }) }) } } diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index 6eb0c60bb..84e3e7bee 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -27,6 +27,7 @@ use util::Mutex; use v1::traits::EthFilter; use v1::types::{BlockNumber, Index, Filter, Log, H256 as RpcH256, U256 as RpcU256}; use v1::helpers::{PollFilter, PollManager}; +use v1::helpers::params::expect_no_params; use v1::impls::eth::pending_logs; /// Eth filter rpc implementation. @@ -76,28 +77,22 @@ impl EthFilter for EthFilterClient where fn new_block_filter(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let mut polls = self.polls.lock(); - let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); - to_value(&RpcU256::from(id)) - }, - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + let mut polls = self.polls.lock(); + let id = polls.create_poll(PollFilter::Block(take_weak!(self.client).chain_info().best_block_number)); + to_value(&RpcU256::from(id)) } fn new_pending_transaction_filter(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let mut polls = self.polls.lock(); - let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); - let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); + try!(expect_no_params(params)); - to_value(&RpcU256::from(id)) - }, - _ => Err(Error::invalid_params()) - } + let mut polls = self.polls.lock(); + let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); + let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); + + to_value(&RpcU256::from(id)) } fn filter_changes(&self, params: Params) -> Result { diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 8cedd865a..98aec52a2 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -23,10 +23,10 @@ use ethcore::client::MiningBlockChainClient; use util::{U256, Address, H256, Mutex}; use transient_hashmap::TransientHashMap; use ethcore::account_provider::AccountProvider; -use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest}; +use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest}; +use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch}; use v1::traits::EthSigning; use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256}; -use v1::impls::{default_gas_price, sign_and_dispatch, request_rejected_error, request_not_found_error, signer_disabled_error}; fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest where C: MiningBlockChainClient, M: MinerService { @@ -151,10 +151,10 @@ impl EthSigning for EthSigningQueueClient let res = match pending.get(&id) { Some(ref promise) => match promise.result() { ConfirmationResult::Waiting => { return Ok(Value::Null); } - ConfirmationResult::Rejected => Err(request_rejected_error()), + ConfirmationResult::Rejected => Err(errors::request_rejected()), ConfirmationResult::Confirmed(rpc_response) => rpc_response, }, - _ => { return Err(request_not_found_error()); } + _ => { return Err(errors::request_not_found()); } }; pending.remove(&id); res @@ -217,16 +217,16 @@ impl EthSigning for EthSigningUnsafeClient where fn post_sign(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(signer_disabled_error()) + Err(errors::signer_disabled()) } fn post_transaction(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(signer_disabled_error()) + Err(errors::signer_disabled()) } fn check_request(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(signer_disabled_error()) + Err(errors::signer_disabled()) } } diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 4d1d59dd8..963869083 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -25,8 +25,8 @@ use jsonrpc_core::*; use ethcore::miner::MinerService; use v1::traits::Ethcore; use v1::types::{Bytes, U256}; -use v1::helpers::{SigningQueue, ConfirmationsQueue, NetworkSettings}; -use v1::impls::signer_disabled_error; +use v1::helpers::{errors, SigningQueue, ConfirmationsQueue, NetworkSettings}; +use v1::helpers::params::expect_no_params; /// Ethcore implementation. pub struct EthcoreClient where @@ -61,64 +61,76 @@ impl EthcoreClient where C: MiningBlockChainClient, M: MinerService impl Ethcore for EthcoreClient where M: MinerService + 'static, C: MiningBlockChainClient + 'static { - fn transactions_limit(&self, _: Params) -> Result { + fn transactions_limit(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&take_weak!(self.miner).transactions_limit()) } - fn min_gas_price(&self, _: Params) -> Result { + fn min_gas_price(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&U256::from(take_weak!(self.miner).minimal_gas_price())) } - fn extra_data(&self, _: Params) -> Result { + fn extra_data(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&Bytes::new(take_weak!(self.miner).extra_data())) } - fn gas_floor_target(&self, _: Params) -> Result { + fn gas_floor_target(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&U256::from(take_weak!(self.miner).gas_floor_target())) } - fn gas_ceil_target(&self, _: Params) -> Result { + fn gas_ceil_target(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&U256::from(take_weak!(self.miner).gas_ceil_target())) } - fn dev_logs(&self, _params: Params) -> Result { + fn dev_logs(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); let logs = self.logger.logs(); to_value(&logs.deref().as_slice()) } - fn dev_logs_levels(&self, _params: Params) -> Result { + fn dev_logs_levels(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&self.logger.levels()) } - fn net_chain(&self, _params: Params) -> Result { + fn net_chain(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&self.settings.chain) } - fn net_max_peers(&self, _params: Params) -> Result { + fn net_max_peers(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&self.settings.max_peers) } - fn net_port(&self, _params: Params) -> Result { + fn net_port(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&self.settings.network_port) } - fn node_name(&self, _params: Params) -> Result { + fn node_name(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); to_value(&self.settings.name) } - fn rpc_settings(&self, _params: Params) -> Result { + fn rpc_settings(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); let mut map = BTreeMap::new(); map.insert("enabled".to_owned(), Value::Bool(self.settings.rpc_enabled)); map.insert("interface".to_owned(), Value::String(self.settings.rpc_interface.clone())); @@ -128,30 +140,29 @@ impl Ethcore for EthcoreClient where M: MinerService + 'static, C: M fn default_extra_data(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => to_value(&Bytes::new(version_data())), - _ => Err(Error::invalid_params()), - } + try!(expect_no_params(params)); + to_value(&Bytes::new(version_data())) } fn gas_price_statistics(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => match take_weak!(self.client).gas_price_statistics(100, 8) { - Ok(stats) => to_value(&stats - .into_iter() - .map(|x| to_value(&U256::from(x)).expect("x must be U256; qed")) - .collect::>()), - _ => Err(Error::internal_error()), - }, - _ => Err(Error::invalid_params()), + try!(expect_no_params(params)); + + match take_weak!(self.client).gas_price_statistics(100, 8) { + Ok(stats) => to_value(&stats + .into_iter() + .map(|x| to_value(&U256::from(x)).expect("x must be U256; qed")) + .collect::>()), + _ => Err(Error::internal_error()), } } - fn unsigned_transactions_count(&self, _params: Params) -> Result { + fn unsigned_transactions_count(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); + match self.confirmations_queue { - None => Err(signer_disabled_error()), + None => Err(errors::signer_disabled()), Some(ref queue) => to_value(&queue.len()), } } diff --git a/rpc/src/v1/impls/ethcore_set.rs b/rpc/src/v1/impls/ethcore_set.rs index 2c02c51ac..a5e037b5f 100644 --- a/rpc/src/v1/impls/ethcore_set.rs +++ b/rpc/src/v1/impls/ethcore_set.rs @@ -20,6 +20,8 @@ use jsonrpc_core::*; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethsync::ManageNetwork; +use v1::helpers::errors; +use v1::helpers::params::expect_no_params; use v1::traits::EthcoreSet; use v1::types::{Bytes, H160, U256}; @@ -117,7 +119,7 @@ impl EthcoreSet for EthcoreSetClient where from_params::<(String,)>(params).and_then(|(peer,)| { match take_weak!(self.net).add_reserved_peer(peer) { Ok(()) => to_value(&true), - Err(_) => Err(Error::invalid_params()), + Err(e) => Err(errors::invalid_params("Peer address", e)), } }) } @@ -127,29 +129,33 @@ impl EthcoreSet for EthcoreSetClient where from_params::<(String,)>(params).and_then(|(peer,)| { match take_weak!(self.net).remove_reserved_peer(peer) { Ok(()) => to_value(&true), - Err(_) => Err(Error::invalid_params()), + Err(e) => Err(errors::invalid_params("Peer address", e)), } }) } - fn drop_non_reserved_peers(&self, _: Params) -> Result { + fn drop_non_reserved_peers(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); take_weak!(self.net).deny_unreserved_peers(); to_value(&true) } - fn accept_non_reserved_peers(&self, _: Params) -> Result { + fn accept_non_reserved_peers(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); take_weak!(self.net).accept_unreserved_peers(); to_value(&true) } - fn start_network(&self, _: Params) -> Result { + fn start_network(&self, params: Params) -> Result { + try!(expect_no_params(params)); take_weak!(self.net).start_network(); Ok(Value::Bool(true)) } - fn stop_network(&self, _: Params) -> Result { + fn stop_network(&self, params: Params) -> Result { + try!(expect_no_params(params)); take_weak!(self.net).stop_network(); Ok(Value::Bool(true)) } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index e21014eff..c0bd76c4c 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -25,21 +25,17 @@ macro_rules! take_weak { } } -macro_rules! rpc_unimplemented { - () => (Err(Error::internal_error())) -} - -mod web3; mod eth; mod eth_filter; mod eth_signing; +mod ethcore; +mod ethcore_set; mod net; mod personal; mod personal_signer; -mod ethcore; -mod ethcore_set; -mod traces; mod rpc; +mod traces; +mod web3; pub use self::web3::Web3Client; pub use self::eth::{EthClient, EthClientOptions}; @@ -53,202 +49,3 @@ pub use self::ethcore_set::EthcoreSetClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; -use serde; -use v1::helpers::TransactionRequest; -use v1::types::{H256 as RpcH256, H520 as RpcH520, BlockNumber}; -use ethcore::error::Error as EthcoreError; -use ethcore::miner::MinerService; -use ethcore::client::MiningBlockChainClient; -use ethcore::transaction::{Action, SignedTransaction, Transaction}; -use ethcore::account_provider::{AccountProvider, Error as AccountError}; -use util::numbers::*; -use util::rlp::encode; -use util::bytes::ToPretty; -use jsonrpc_core::{Error, ErrorCode, Value, to_value, from_params, Params}; - -mod error_codes { - // NOTE [ToDr] Codes from [-32099, -32000] - pub const UNSUPPORTED_REQUEST_CODE: i64 = -32000; - pub const NO_WORK_CODE: i64 = -32001; - pub const NO_AUTHOR_CODE: i64 = -32002; - pub const UNKNOWN_ERROR: i64 = -32009; - pub const TRANSACTION_ERROR: i64 = -32010; - pub const ACCOUNT_LOCKED: i64 = -32020; - pub const PASSWORD_INVALID: i64 = -32021; - pub const SIGNER_DISABLED: i64 = -32030; - pub const REQUEST_REJECTED: i64 = -32040; - pub const REQUEST_NOT_FOUND: i64 = -32041; -} - -fn params_len(params: &Params) -> usize { - match params { - &Params::Array(ref vec) => vec.len(), - _ => 0, - } -} - -/// Deserialize request parameters with optional second parameter `BlockNumber` defaulting to `BlockNumber::Latest`. -pub fn from_params_default_second(params: Params) -> Result<(F, BlockNumber, ), Error> where F: serde::de::Deserialize { - match params_len(¶ms) { - 1 => from_params::<(F, )>(params).map(|(f,)| (f, BlockNumber::Latest)), - _ => from_params::<(F, BlockNumber)>(params), - } -} - -/// Deserialize request parameters with optional third parameter `BlockNumber` defaulting to `BlockNumber::Latest`. -pub fn from_params_default_third(params: Params) -> Result<(F1, F2, BlockNumber, ), Error> where F1: serde::de::Deserialize, F2: serde::de::Deserialize { - match params_len(¶ms) { - 2 => from_params::<(F1, F2, )>(params).map(|(f1, f2)| (f1, f2, BlockNumber::Latest)), - _ => from_params::<(F1, F2, BlockNumber)>(params) - } -} - - -fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result - where C: MiningBlockChainClient, M: MinerService { - let hash = RpcH256::from(signed_transaction.hash()); - - let import = miner.import_own_transaction(client, signed_transaction); - - import - .map_err(transaction_error) - .and_then(|_| to_value(&hash)) -} - -fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result { - accounts.sign_with_password(address, pass, hash) - .map_err(password_error) - .and_then(|hash| to_value(&RpcH520::from(hash))) -} - -fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { - Transaction { - nonce: request.nonce - .or_else(|| miner - .last_nonce(&request.from) - .map(|nonce| nonce + U256::one())) - .unwrap_or_else(|| client.latest_nonce(&request.from)), - - action: request.to.map_or(Action::Create, Action::Call), - gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), - gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), - value: request.value.unwrap_or_else(U256::zero), - data: request.data.map_or_else(Vec::new, |b| b.to_vec()), - } -} - -fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result - where C: MiningBlockChainClient, M: MinerService { - - let address = request.from; - let signed_transaction = { - let t = prepare_transaction(client, miner, request); - let hash = t.hash(); - let signature = try!(account_provider.sign_with_password(address, password, hash).map_err(password_error)); - t.with_signature(signature) - }; - - trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); - dispatch_transaction(&*client, &*miner, signed_transaction) -} - -fn sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address) -> Result - where C: MiningBlockChainClient, M: MinerService { - - let signed_transaction = { - let t = prepare_transaction(client, miner, request); - let hash = t.hash(); - let signature = try!(account_provider.sign(address, hash).map_err(signing_error)); - t.with_signature(signature) - }; - - trace!(target: "miner", "send_transaction: dispatching tx: {}", encode(&signed_transaction).to_vec().pretty()); - dispatch_transaction(&*client, &*miner, signed_transaction) -} - -fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { - client - .gas_price_statistics(100, 8) - .map(|x| x[4]) - .unwrap_or_else(|_| miner.sensible_gas_price()) -} - -fn signer_disabled_error() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED), - message: "Trusted Signer is disabled. This API is not available.".into(), - data: None - } -} - -fn signing_error(error: AccountError) -> Error { - Error { - code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED), - message: "Your account is locked. Unlock the account via CLI, personal_unlockAccount or use Trusted Signer.".into(), - data: Some(Value::String(format!("{:?}", error))), - } -} - -fn password_error(error: AccountError) -> Error { - Error { - code: ErrorCode::ServerError(error_codes::PASSWORD_INVALID), - message: "Account password is invalid or account does not exist.".into(), - data: Some(Value::String(format!("{:?}", error))), - } -} - -/// Error returned when request is rejected (in Trusted Signer). -pub fn request_rejected_error() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::REQUEST_REJECTED), - message: "Request has been rejected.".into(), - data: None, - } -} - -/// Error returned when request is not found in queue. -pub fn request_not_found_error() -> Error { - Error { - code: ErrorCode::ServerError(error_codes::REQUEST_NOT_FOUND), - message: "Request not found.".into(), - data: None, - } -} - -fn transaction_error(error: EthcoreError) -> Error { - use ethcore::error::TransactionError::*; - - if let EthcoreError::Transaction(e) = error { - let msg = match e { - AlreadyImported => "Transaction with the same hash was already imported.".into(), - Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), - TooCheapToReplace => { - "Transaction fee is too low. There is another transaction with same nonce in the queue. Try increasing the fee or incrementing the nonce.".into() - }, - LimitReached => { - "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() - }, - InsufficientGasPrice { minimal, got } => { - format!("Transaction fee is too low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", minimal, got) - }, - InsufficientBalance { balance, cost } => { - format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) - }, - GasLimitExceeded { limit, got } => { - format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) - }, - InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), - }; - Error { - code: ErrorCode::ServerError(error_codes::TRANSACTION_ERROR), - message: msg, - data: None, - } - } else { - Error { - code: ErrorCode::ServerError(error_codes::UNKNOWN_ERROR), - message: "Unknown error when sending transaction.".into(), - data: Some(Value::String(format!("{:?}", error))), - } - } -} diff --git a/rpc/src/v1/impls/net.rs b/rpc/src/v1/impls/net.rs index 0bbfeca6c..9c22a3638 100644 --- a/rpc/src/v1/impls/net.rs +++ b/rpc/src/v1/impls/net.rs @@ -19,6 +19,7 @@ use std::sync::{Arc, Weak}; use jsonrpc_core::*; use ethsync::SyncProvider; use v1::traits::Net; +use v1::helpers::params::expect_no_params; /// Net rpc implementation. pub struct NetClient where S: SyncProvider { @@ -35,15 +36,18 @@ impl NetClient where S: SyncProvider { } impl Net for NetClient where S: SyncProvider + 'static { - fn version(&self, _: Params) -> Result { + fn version(&self, params: Params) -> Result { + try!(expect_no_params(params)); Ok(Value::String(format!("{}", take_weak!(self.sync).status().network_id).to_owned())) } - fn peer_count(&self, _params: Params) -> Result { + fn peer_count(&self, params: Params) -> Result { + try!(expect_no_params(params)); Ok(Value::String(format!("0x{:x}", take_weak!(self.sync).status().num_peers as u64).to_owned())) } - fn is_listening(&self, _: Params) -> Result { + fn is_listening(&self, params: Params) -> Result { + try!(expect_no_params(params)); // right now (11 march 2016), we are always listening for incoming connections Ok(Value::Bool(true)) } diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 958efe3a8..26156ca23 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -20,8 +20,9 @@ use std::collections::{BTreeMap}; use jsonrpc_core::*; use v1::traits::Personal; use v1::types::{H160 as RpcH160, TransactionRequest}; -use v1::impls::unlock_sign_and_dispatch; -use v1::helpers::{TransactionRequest as TRequest}; +use v1::helpers::{errors, TransactionRequest as TRequest}; +use v1::helpers::params::expect_no_params; +use v1::helpers::dispatch::unlock_sign_and_dispatch; use ethcore::account_provider::AccountProvider; use util::Address; use ethcore::client::MiningBlockChainClient; @@ -57,8 +58,10 @@ impl PersonalClient where C: MiningBlockChainClient, M: MinerService impl Personal for PersonalClient where C: MiningBlockChainClient, M: MinerService { - fn signer_enabled(&self, _: Params) -> Result { + fn signer_enabled(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); + self.signer_port .map(|v| to_value(&v)) .unwrap_or_else(|| to_value(&false)) @@ -66,14 +69,11 @@ impl Personal for PersonalClient where C: MiningBl fn accounts(&self, params: Params) -> Result { try!(self.active()); - match params { - Params::None => { - let store = take_weak!(self.accounts); - let accounts = try!(store.accounts().map_err(|_| Error::internal_error())); - to_value(&accounts.into_iter().map(Into::into).collect::>()) - }, - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + + let store = take_weak!(self.accounts); + let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e))); + to_value(&accounts.into_iter().map(Into::into).collect::>()) } fn new_account(&self, params: Params) -> Result { @@ -83,7 +83,7 @@ impl Personal for PersonalClient where C: MiningBl let store = take_weak!(self.accounts); match store.new_account(&pass) { Ok(address) => to_value(&RpcH160::from(address)), - Err(_) => Err(Error::internal_error()) + Err(e) => Err(errors::account("Could not create account.", e)), } } ) @@ -124,7 +124,7 @@ impl Personal for PersonalClient where C: MiningBl let store = take_weak!(self.accounts); from_params::<(RpcH160, _)>(params).and_then(|(addr, name)| { let addr: Address = addr.into(); - store.set_account_name(addr, name).map_err(|_| Error::invalid_params()).map(|_| Value::Null) + store.set_account_name(addr, name).map_err(|e| errors::account("Could not set account name.", e)).map(|_| Value::Null) }) } @@ -133,14 +133,16 @@ impl Personal for PersonalClient where C: MiningBl let store = take_weak!(self.accounts); from_params::<(RpcH160, _)>(params).and_then(|(addr, meta)| { let addr: Address = addr.into(); - store.set_account_meta(addr, meta).map_err(|_| Error::invalid_params()).map(|_| Value::Null) + store.set_account_meta(addr, meta).map_err(|e| errors::account("Could not set account meta.", e)).map(|_| Value::Null) }) } - fn accounts_info(&self, _: Params) -> Result { + fn accounts_info(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); let store = take_weak!(self.accounts); - Ok(Value::Object(try!(store.accounts_info().map_err(|_| Error::invalid_params())).into_iter().map(|(a, v)| { + let info = try!(store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))); + Ok(Value::Object(info.into_iter().map(|(a, v)| { let m = map![ "name".to_owned() => to_value(&v.name).unwrap(), "meta".to_owned() => to_value(&v.meta).unwrap(), diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 2dfce57af..bd58fde7d 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -23,8 +23,9 @@ use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; use v1::traits::PersonalSigner; use v1::types::{TransactionModification, ConfirmationRequest, U256}; -use v1::impls::{unlock_sign_and_dispatch, signature_with_password}; -use v1::helpers::{SigningQueue, ConfirmationsQueue, ConfirmationPayload}; +use v1::helpers::{errors, SigningQueue, ConfirmationsQueue, ConfirmationPayload}; +use v1::helpers::params::expect_no_params; +use v1::helpers::dispatch::{unlock_sign_and_dispatch, signature_with_password}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -55,8 +56,9 @@ impl SignerClient where C: MiningBlockChainClient, impl PersonalSigner for SignerClient where C: MiningBlockChainClient, M: MinerService { - fn requests_to_confirm(&self, _params: Params) -> Result { + fn requests_to_confirm(&self, params: Params) -> Result { try!(self.active()); + try!(expect_no_params(params)); let queue = take_weak!(self.queue); to_value(&queue.requests().into_iter().map(From::from).collect::>()) } @@ -91,7 +93,7 @@ impl PersonalSigner for SignerClient where C: Mini queue.request_confirmed(id, Ok(response.clone())); } result - }).unwrap_or_else(|| Err(Error::invalid_params())) + }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) } ) } diff --git a/rpc/src/v1/impls/rpc.rs b/rpc/src/v1/impls/rpc.rs index ebbef5025..fafc92fe5 100644 --- a/rpc/src/v1/impls/rpc.rs +++ b/rpc/src/v1/impls/rpc.rs @@ -18,6 +18,7 @@ use std::collections::BTreeMap; use jsonrpc_core::*; use v1::traits::Rpc; +use v1::helpers::params::expect_no_params; /// RPC generic methods implementation. pub struct RpcClient { @@ -39,7 +40,8 @@ impl RpcClient { } impl Rpc for RpcClient { - fn rpc_modules(&self, _: Params) -> Result { + fn rpc_modules(&self, params: Params) -> Result { + try!(expect_no_params(params)); let modules = self.modules.iter() .fold(BTreeMap::new(), |mut map, (k, v)| { map.insert(k.to_owned(), Value::String(v.to_owned())); @@ -48,7 +50,8 @@ impl Rpc for RpcClient { Ok(Value::Object(modules)) } - fn modules(&self, _: Params) -> Result { + fn modules(&self, params: Params) -> Result { + try!(expect_no_params(params)); let modules = self.modules.iter() .filter(|&(k, _v)| { self.valid_apis.contains(k) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index d8d2b0257..e5f84d9ef 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -23,9 +23,9 @@ use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::helpers::CallRequest as CRequest; +use v1::helpers::{errors, CallRequest as CRequest}; +use v1::helpers::params::from_params_default_third; use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; -use v1::impls::from_params_default_third; fn to_call_analytics(flags: Vec) -> CallAnalytics { CallAnalytics { @@ -144,7 +144,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: Ok(e) => to_value(&TraceResults::from(e)), _ => Ok(Value::Null), }, - Err(_) => Err(Error::invalid_params()), + Err(e) => Err(errors::invalid_params("Transaction is not valid RLP", e)), } }) } diff --git a/rpc/src/v1/impls/web3.rs b/rpc/src/v1/impls/web3.rs index 3f993f21c..d4ff4f7aa 100644 --- a/rpc/src/v1/impls/web3.rs +++ b/rpc/src/v1/impls/web3.rs @@ -19,6 +19,7 @@ use jsonrpc_core::*; use util::version; use v1::traits::Web3; use v1::types::{H256, Bytes}; +use v1::helpers::params::expect_no_params; use util::sha3::Hashable; /// Web3 rpc implementation. @@ -31,10 +32,8 @@ impl Web3Client { impl Web3 for Web3Client { fn client_version(&self, params: Params) -> Result { - match params { - Params::None => Ok(Value::String(version().to_owned().replace("Parity/", "Parity//"))), - _ => Err(Error::invalid_params()) - } + try!(expect_no_params(params)); + Ok(Value::String(version().to_owned().replace("Parity/", "Parity//"))) } fn sha3(&self, params: Params) -> Result { diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index c462bd333..897fcf623 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -18,8 +18,9 @@ //! //! Compliant with ethereum rpc. -mod impls; +#[macro_use] mod helpers; +mod impls; pub mod traits; pub mod tests; diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 5f932451d..d5b8fd5f6 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -12,7 +12,7 @@ rustc_version = "0.1" [dependencies] rand = "0.3.14" -jsonrpc-core = "2.0" +jsonrpc-core = "2.1" log = "0.3" env_logger = "0.3" ws = { git = "https://github.com/ethcore/ws-rs.git", branch = "stable" } From 2f1ade8116b9324b52d97747282226db5c0db72c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 8 Aug 2016 18:41:30 +0200 Subject: [PATCH 8/8] take snapshot at specified block and slightly better informants (#1873) * prettier informant for snapshot creation * allow taking snapshot at a given block * minor tweaks * elaborate on cli --- ethcore/src/client/client.rs | 36 +++++++++---- ethcore/src/snapshot/error.rs | 13 ++++- ethcore/src/snapshot/mod.rs | 80 +++++++++++++++++++++++----- ethcore/src/snapshot/tests/blocks.rs | 4 +- ethcore/src/snapshot/tests/state.rs | 4 +- parity/cli.rs | 7 +++ parity/configuration.rs | 2 + parity/snapshot.rs | 33 +++++++++++- 8 files changed, 148 insertions(+), 31 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e383ce193..bf9c86633 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -594,19 +594,35 @@ impl Client { } } - /// Take a snapshot. - pub fn take_snapshot(&self, writer: W) -> Result<(), ::error::Error> { + /// Take a snapshot at the given block. + /// If the ID given is "latest", this will default to 1000 blocks behind. + pub fn take_snapshot(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), ::error::Error> { let db = self.state_db.lock().boxed_clone(); let best_block_number = self.chain_info().best_block_number; - let start_block_number = if best_block_number > 1000 { - best_block_number - 1000 - } else { - 0 - }; - let start_hash = self.block_hash(BlockID::Number(start_block_number)) - .expect("blocks within HISTORY are always stored."); + let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); - try!(snapshot::take_snapshot(&self.chain, start_hash, db.as_hashdb(), writer)); + if best_block_number > HISTORY + block_number && db.is_pruned() { + return Err(snapshot::Error::OldBlockPrunedDB.into()); + } + + let start_hash = match at { + BlockID::Latest => { + let start_num = if best_block_number > 1000 { + best_block_number - 1000 + } else { + 0 + }; + + self.block_hash(BlockID::Number(start_num)) + .expect("blocks within HISTORY are always stored.") + } + _ => match self.block_hash(at) { + Some(hash) => hash, + None => return Err(snapshot::Error::InvalidStartingBlock(at).into()), + }, + }; + + try!(snapshot::take_snapshot(&self.chain, start_hash, db.as_hashdb(), writer, p)); Ok(()) } diff --git a/ethcore/src/snapshot/error.rs b/ethcore/src/snapshot/error.rs index 98f906ec5..be4ed39d2 100644 --- a/ethcore/src/snapshot/error.rs +++ b/ethcore/src/snapshot/error.rs @@ -18,6 +18,8 @@ use std::fmt; +use ids::BlockID; + use util::H256; use util::trie::TrieError; use util::rlp::DecoderError; @@ -26,9 +28,13 @@ use util::rlp::DecoderError; #[derive(Debug)] pub enum Error { /// Invalid starting block for snapshot. - InvalidStartingBlock(H256), + InvalidStartingBlock(BlockID), /// Block not found. BlockNotFound(H256), + /// Incomplete chain. + IncompleteChain, + /// Old starting block in a pruned database. + OldBlockPrunedDB, /// Trie error. Trie(TrieError), /// Decoder error. @@ -40,8 +46,11 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::InvalidStartingBlock(ref hash) => write!(f, "Invalid starting block hash: {}", hash), + Error::InvalidStartingBlock(ref id) => write!(f, "Invalid starting block: {:?}", id), Error::BlockNotFound(ref hash) => write!(f, "Block not found in chain: {}", hash), + Error::IncompleteChain => write!(f, "Cannot create snapshot due to incomplete chain."), + Error::OldBlockPrunedDB => write!(f, "Attempted to create a snapshot at an old block while using \ + a pruned database. Please re-run with the --pruning archive flag."), Error::Io(ref err) => err.fmt(f), Error::Decoder(ref err) => err.fmt(f), Error::Trie(ref err) => err.fmt(f), diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 5784ed936..9dbbf1d9a 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -18,10 +18,12 @@ use std::collections::VecDeque; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use account_db::{AccountDB, AccountDBMut}; use blockchain::{BlockChain, BlockProvider}; use engines::Engine; +use ids::BlockID; use views::BlockView; use util::{Bytes, Hashable, HashDB, snappy, TrieDB, TrieDBMut, TrieMut}; @@ -58,9 +60,49 @@ const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024; // How many blocks to include in a snapshot, starting from the head of the chain. const SNAPSHOT_BLOCKS: u64 = 30000; +/// A progress indicator for snapshots. +#[derive(Debug)] +pub struct Progress { + accounts: AtomicUsize, + blocks: AtomicUsize, + size: AtomicUsize, // Todo [rob] use Atomicu64 when it stabilizes. + done: AtomicBool, +} + +impl Progress { + /// Create a new progress indicator. + pub fn new() -> Self { + Progress { + accounts: AtomicUsize::new(0), + blocks: AtomicUsize::new(0), + size: AtomicUsize::new(0), + done: AtomicBool::new(false), + } + } + + /// Get the number of accounts snapshotted thus far. + pub fn accounts(&self) -> usize { self.accounts.load(Ordering::Relaxed) } + + /// Get the number of blocks snapshotted thus far. + pub fn blocks(&self) -> usize { self.blocks.load(Ordering::Relaxed) } + + /// Get the written size of the snapshot in bytes. + pub fn size(&self) -> usize { self.size.load(Ordering::Relaxed) } + + /// Whether the snapshot is complete. + pub fn done(&self) -> bool { self.done.load(Ordering::SeqCst) } + +} /// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer. -pub fn take_snapshot(chain: &BlockChain, start_block_hash: H256, state_db: &HashDB, writer: W) -> Result<(), Error> { - let start_header = try!(chain.block_header(&start_block_hash).ok_or(Error::InvalidStartingBlock(start_block_hash))); +pub fn take_snapshot( + chain: &BlockChain, + block_at: H256, + state_db: &HashDB, + writer: W, + p: &Progress +) -> Result<(), Error> { + let start_header = try!(chain.block_header(&block_at) + .ok_or(Error::InvalidStartingBlock(BlockID::Hash(block_at)))); let state_root = start_header.state_root(); let number = start_header.number(); @@ -68,8 +110,8 @@ pub fn take_snapshot(chain: &BlockChain, start_block_h let writer = Mutex::new(writer); let (state_hashes, block_hashes) = try!(scope(|scope| { - let block_guard = scope.spawn(|| chunk_blocks(chain, (number, start_block_hash), &writer)); - let state_res = chunk_state(state_db, state_root, &writer); + let block_guard = scope.spawn(|| chunk_blocks(chain, (number, block_at), &writer, p)); + let state_res = chunk_state(state_db, state_root, &writer, p); state_res.and_then(|state_hashes| { block_guard.join().map(|block_hashes| (state_hashes, block_hashes)) @@ -83,11 +125,13 @@ pub fn take_snapshot(chain: &BlockChain, start_block_h block_hashes: block_hashes, state_root: *state_root, block_number: number, - block_hash: start_block_hash, + block_hash: block_at, }; try!(writer.into_inner().finish(manifest_data)); + p.done.store(true, Ordering::SeqCst); + Ok(()) } @@ -100,6 +144,7 @@ struct BlockChunker<'a> { hashes: Vec, snappy_buffer: Vec, writer: &'a Mutex, + progress: &'a Progress, } impl<'a> BlockChunker<'a> { @@ -162,7 +207,8 @@ impl<'a> BlockChunker<'a> { let parent_total_difficulty = parent_details.total_difficulty; - let mut rlp_stream = RlpStream::new_list(3 + self.rlps.len()); + let num_entries = self.rlps.len(); + let mut rlp_stream = RlpStream::new_list(3 + num_entries); rlp_stream.append(&parent_number).append(&parent_hash).append(&parent_total_difficulty); for pair in self.rlps.drain(..) { @@ -178,6 +224,9 @@ impl<'a> BlockChunker<'a> { try!(self.writer.lock().write_block_chunk(hash, compressed)); trace!(target: "snapshot", "wrote block chunk. hash: {}, size: {}, uncompressed size: {}", hash.hex(), size, raw_data.len()); + self.progress.size.fetch_add(size, Ordering::SeqCst); + self.progress.blocks.fetch_add(num_entries, Ordering::SeqCst); + self.hashes.push(hash); Ok(()) } @@ -189,7 +238,7 @@ impl<'a> BlockChunker<'a> { /// The path parameter is the directory to store the block chunks in. /// This function assumes the directory exists already. /// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis. -pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), writer: &Mutex) -> Result, Error> { +pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), writer: &Mutex, progress: &'a Progress) -> Result, Error> { let (start_number, start_hash) = start_block_info; let first_hash = if start_number < SNAPSHOT_BLOCKS { @@ -197,8 +246,7 @@ pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), wr chain.genesis_hash() } else { let first_num = start_number - SNAPSHOT_BLOCKS; - chain.block_hash(first_num) - .expect("number before best block number; whole chain is stored; qed") + try!(chain.block_hash(first_num).ok_or(Error::IncompleteChain)) }; let mut chunker = BlockChunker { @@ -208,6 +256,7 @@ pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_block_info: (u64, H256), wr hashes: Vec::new(), snappy_buffer: vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)], writer: writer, + progress: progress, }; try!(chunker.chunk_all(first_hash)); @@ -222,6 +271,7 @@ struct StateChunker<'a> { cur_size: usize, snappy_buffer: Vec, writer: &'a Mutex, + progress: &'a Progress, } impl<'a> StateChunker<'a> { @@ -249,7 +299,8 @@ impl<'a> StateChunker<'a> { // Write out the buffer to disk, pushing the created chunk's hash to // the list. fn write_chunk(&mut self) -> Result<(), Error> { - let mut stream = RlpStream::new_list(self.rlps.len()); + let num_entries = self.rlps.len(); + let mut stream = RlpStream::new_list(num_entries); for rlp in self.rlps.drain(..) { stream.append_raw(&rlp, 1); } @@ -263,6 +314,9 @@ impl<'a> StateChunker<'a> { try!(self.writer.lock().write_state_chunk(hash, compressed)); trace!(target: "snapshot", "wrote state chunk. size: {}, uncompressed size: {}", compressed_size, raw_data.len()); + self.progress.accounts.fetch_add(num_entries, Ordering::SeqCst); + self.progress.size.fetch_add(compressed_size, Ordering::SeqCst); + self.hashes.push(hash); self.cur_size = 0; @@ -275,7 +329,7 @@ impl<'a> StateChunker<'a> { /// /// Returns a list of hashes of chunks created, or any error it may /// have encountered. -pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex) -> Result, Error> { +pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex, progress: &'a Progress) -> Result, Error> { let account_trie = try!(TrieDB::new(db, &root)); let mut chunker = StateChunker { @@ -284,10 +338,9 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex, pub flag_jitvm: bool, pub flag_log_file: Option, diff --git a/parity/configuration.rs b/parity/configuration.rs index 18d54f91c..17a0bef29 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -171,6 +171,7 @@ impl Configuration { file_path: self.args.arg_file.clone(), wal: wal, kind: snapshot::Kind::Take, + block_at: try!(to_block_id(&self.args.flag_at)), }; Cmd::Snapshot(snapshot_cmd) } else if self.args.cmd_restore { @@ -186,6 +187,7 @@ impl Configuration { file_path: self.args.arg_file.clone(), wal: wal, kind: snapshot::Kind::Restore, + block_at: try!(to_block_id("latest")), // unimportant. }; Cmd::Snapshot(restore_cmd) } else { diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 790e001e6..70abf8fc1 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -19,12 +19,15 @@ use std::time::Duration; use std::path::{Path, PathBuf}; use std::sync::Arc; + use ethcore_logger::{setup_log, Config as LogConfig}; -use ethcore::snapshot::{RestorationStatus, SnapshotService}; +use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService}; use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter}; use ethcore::service::ClientService; use ethcore::client::{Mode, DatabaseCompactionProfile, Switch, VMType}; use ethcore::miner::Miner; +use ethcore::ids::BlockID; + use cache::CacheConfig; use params::{SpecType, Pruning}; use helpers::{to_client_config, execute_upgrades}; @@ -56,6 +59,7 @@ pub struct SnapshotCommand { pub file_path: Option, pub wal: bool, pub kind: Kind, + pub block_at: BlockID, } impl SnapshotCommand { @@ -168,6 +172,7 @@ impl SnapshotCommand { pub fn take_snapshot(self) -> Result<(), String> { let file_path = try!(self.file_path.clone().ok_or("No file path provided.".to_owned())); let file_path: PathBuf = file_path.into(); + let block_at = self.block_at.clone(); let (service, _panic_handler) = try!(self.start_service()); warn!("Snapshots are currently experimental. File formats may be subject to change."); @@ -175,11 +180,35 @@ impl SnapshotCommand { let writer = try!(PackedWriter::new(&file_path) .map_err(|e| format!("Failed to open snapshot writer: {}", e))); - if let Err(e) = service.client().take_snapshot(writer) { + let progress = Arc::new(Progress::new()); + let p = progress.clone(); + let informant_handle = ::std::thread::spawn(move || { + ::std::thread::sleep(Duration::from_secs(5)); + + let mut last_size = 0; + while !p.done() { + let cur_size = p.size(); + if cur_size != last_size { + last_size = cur_size; + info!("Snapshot: {} accounts {} blocks {} bytes", p.accounts(), p.blocks(), p.size()); + } else { + info!("Snapshot: No progress since last update."); + } + + ::std::thread::sleep(Duration::from_secs(5)); + } + }); + + if let Err(e) = service.client().take_snapshot(writer, block_at, &*progress) { let _ = ::std::fs::remove_file(&file_path); return Err(format!("Encountered fatal error while creating snapshot: {}", e)); } + info!("snapshot creation complete"); + + assert!(progress.done()); + try!(informant_handle.join().map_err(|_| "failed to join logger thread")); + Ok(()) } }