diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index bd9e3e1fd..ee14ad976 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -430,7 +430,11 @@ impl LightProtocol { // compute and deduct cost. let pre_creds = creds.current(); - let cost = params.compute_cost_multi(requests.requests()); + let cost = match params.compute_cost_multi(requests.requests()) { + Some(cost) => cost, + None => return Err(Error::NotServer), + }; + creds.deduct_cost(cost)?; trace!(target: "pip", "requesting from peer {}. Cost: {}; Available: {}", @@ -924,7 +928,7 @@ impl LightProtocol { peer.local_credits.deduct_cost(peer.local_flow.base_cost())?; for request_rlp in raw.at(1)?.iter().take(MAX_REQUESTS) { let request: Request = request_rlp.as_val()?; - let cost = peer.local_flow.compute_cost(&request); + let cost = peer.local_flow.compute_cost(&request).ok_or(Error::NotServer)?; peer.local_credits.deduct_cost(cost)?; request_builder.push(request).map_err(|_| Error::BadBackReference)?; } @@ -939,7 +943,7 @@ impl LightProtocol { match complete_req { CompleteRequest::Headers(req) => self.provider.block_headers(req).map(Response::Headers), CompleteRequest::HeaderProof(req) => self.provider.header_proof(req).map(Response::HeaderProof), - CompleteRequest::TransactionIndex(_) => None, // don't answer these yet, but leave them in protocol. + CompleteRequest::TransactionIndex(req) => self.provider.transaction_index(req).map(Response::TransactionIndex), CompleteRequest::Body(req) => self.provider.block_body(req).map(Response::Body), CompleteRequest::Receipts(req) => self.provider.block_receipts(req).map(Response::Receipts), CompleteRequest::Account(req) => self.provider.account_proof(req).map(Response::Account), diff --git a/ethcore/light/src/net/request_credits.rs b/ethcore/light/src/net/request_credits.rs index 8c2e89eec..5007d4d6c 100644 --- a/ethcore/light/src/net/request_credits.rs +++ b/ethcore/light/src/net/request_credits.rs @@ -79,19 +79,42 @@ impl Credits { } /// A cost table, mapping requests to base and per-request costs. +/// Costs themselves may be missing. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CostTable { base: U256, // cost per packet. - headers: U256, // cost per header - transaction_index: U256, - body: U256, - receipts: U256, - account: U256, - storage: U256, - code: U256, - header_proof: U256, - transaction_proof: U256, // cost per gas. - epoch_signal: U256, + headers: Option, // cost per header + transaction_index: Option, + body: Option, + receipts: Option, + account: Option, + storage: Option, + code: Option, + header_proof: Option, + transaction_proof: Option, // cost per gas. + epoch_signal: Option, +} + +impl CostTable { + fn costs_set(&self) -> usize { + let mut num_set = 0; + + { + let mut incr_if_set = |cost: &Option<_>| if cost.is_some() { num_set += 1 }; + incr_if_set(&self.headers); + incr_if_set(&self.transaction_index); + incr_if_set(&self.body); + incr_if_set(&self.receipts); + incr_if_set(&self.account); + incr_if_set(&self.storage); + incr_if_set(&self.code); + incr_if_set(&self.header_proof); + incr_if_set(&self.transaction_proof); + incr_if_set(&self.epoch_signal); + } + + num_set + } } impl Default for CostTable { @@ -99,31 +122,32 @@ impl Default for CostTable { // arbitrarily chosen constants. CostTable { base: 100000.into(), - headers: 10000.into(), - transaction_index: 10000.into(), - body: 15000.into(), - receipts: 5000.into(), - account: 25000.into(), - storage: 25000.into(), - code: 20000.into(), - header_proof: 15000.into(), - transaction_proof: 2.into(), - epoch_signal: 10000.into(), + headers: Some(10000.into()), + transaction_index: Some(10000.into()), + body: Some(15000.into()), + receipts: Some(5000.into()), + account: Some(25000.into()), + storage: Some(25000.into()), + code: Some(20000.into()), + header_proof: Some(15000.into()), + transaction_proof: Some(2.into()), + epoch_signal: Some(10000.into()), } } } impl Encodable for CostTable { fn rlp_append(&self, s: &mut RlpStream) { - fn append_cost(s: &mut RlpStream, cost: &U256, kind: request::Kind) { - s.begin_list(2); - - // hack around https://github.com/paritytech/parity/issues/4356 - Encodable::rlp_append(&kind, s); - s.append(cost); + fn append_cost(s: &mut RlpStream, cost: &Option, kind: request::Kind) { + if let Some(ref cost) = *cost { + s.begin_list(2); + // hack around https://github.com/paritytech/parity/issues/4356 + Encodable::rlp_append(&kind, s); + s.append(cost); + } } - s.begin_list(11).append(&self.base); + s.begin_list(1 + self.costs_set()).append(&self.base); append_cost(s, &self.headers, request::Kind::Headers); append_cost(s, &self.transaction_index, request::Kind::TransactionIndex); append_cost(s, &self.body, request::Kind::Body); @@ -168,21 +192,25 @@ impl Decodable for CostTable { } } - let unwrap_cost = |cost: Option| cost.ok_or(DecoderError::Custom("Not all costs specified in cost table.")); - - Ok(CostTable { + let table = CostTable { base: base, - headers: unwrap_cost(headers)?, - transaction_index: unwrap_cost(transaction_index)?, - body: unwrap_cost(body)?, - receipts: unwrap_cost(receipts)?, - account: unwrap_cost(account)?, - storage: unwrap_cost(storage)?, - code: unwrap_cost(code)?, - header_proof: unwrap_cost(header_proof)?, - transaction_proof: unwrap_cost(transaction_proof)?, - epoch_signal: unwrap_cost(epoch_signal)?, - }) + headers: headers, + transaction_index: transaction_index, + body: body, + receipts: receipts, + account: account, + storage: storage, + code: code, + header_proof: header_proof, + transaction_proof: transaction_proof, + epoch_signal: epoch_signal, + }; + + if table.costs_set() == 0 { + Err(DecoderError::Custom("no cost types set.")) + } else { + Ok(table) + } } } @@ -230,7 +258,7 @@ impl FlowParams { let serve_per_second = serve_per_second.max(1.0 / 10_000.0); // as a percentage of the recharge per second. - U256::from((recharge as f64 / serve_per_second) as u64) + Some(U256::from((recharge as f64 / serve_per_second) as u64)) }; let costs = CostTable { @@ -256,12 +284,12 @@ impl FlowParams { /// Create effectively infinite flow params. pub fn free() -> Self { - let free_cost: U256 = 0.into(); + let free_cost: Option = Some(0.into()); FlowParams { limit: (!0u64).into(), recharge: 1.into(), costs: CostTable { - base: free_cost.clone(), + base: 0.into(), headers: free_cost.clone(), transaction_index: free_cost.clone(), body: free_cost.clone(), @@ -290,9 +318,9 @@ impl FlowParams { /// Compute the actual cost of a request, given the kind of request /// and number of requests made. - pub fn compute_cost(&self, request: &Request) -> U256 { + pub fn compute_cost(&self, request: &Request) -> Option { match *request { - Request::Headers(ref req) => self.costs.headers * req.max.into(), + Request::Headers(ref req) => self.costs.headers.map(|c| c * req.max.into()), Request::HeaderProof(_) => self.costs.header_proof, Request::TransactionIndex(_) => self.costs.transaction_index, Request::Body(_) => self.costs.body, @@ -300,15 +328,23 @@ impl FlowParams { Request::Account(_) => self.costs.account, Request::Storage(_) => self.costs.storage, Request::Code(_) => self.costs.code, - Request::Execution(ref req) => self.costs.transaction_proof * req.gas, + Request::Execution(ref req) => self.costs.transaction_proof.map(|c| c * req.gas), Request::Signal(_) => self.costs.epoch_signal, } } /// Compute the cost of a set of requests. /// This is the base cost plus the cost of each individual request. - pub fn compute_cost_multi(&self, requests: &[Request]) -> U256 { - requests.iter().fold(self.costs.base, |cost, req| cost + self.compute_cost(req)) + pub fn compute_cost_multi(&self, requests: &[Request]) -> Option { + let mut cost = self.costs.base; + for request in requests { + match self.compute_cost(request) { + Some(c) => cost = cost + c, + None => return None, + } + } + + Some(cost) } /// Create initial credits. @@ -408,6 +444,6 @@ mod tests { ); assert_eq!(flow_params2.costs, flow_params3.costs); - assert_eq!(flow_params.costs.headers, flow_params2.costs.headers * 2.into()); + assert_eq!(flow_params.costs.headers.unwrap(), flow_params2.costs.headers.unwrap() * 2.into()); } } diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index af0a02c3e..0e344d803 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -116,6 +116,16 @@ impl Provider for TestProvider { self.0.client.block_header(id) } + fn transaction_index(&self, req: request::CompleteTransactionIndexRequest) + -> Option + { + Some(request::TransactionIndexResponse { + num: 100, + hash: req.hash, + index: 55, + }) + } + fn block_body(&self, req: request::CompleteBodyRequest) -> Option { self.0.client.block_body(req) } @@ -308,7 +318,7 @@ fn get_block_headers() { let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect(); assert_eq!(headers.len(), 10); - let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()); + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); let response = vec![Response::Headers(HeadersResponse { headers: headers, @@ -361,7 +371,7 @@ fn get_block_bodies() { let request_body = make_packet(req_id, &requests); let response = { - let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()); + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); let mut response_stream = RlpStream::new_list(3); response_stream.append(&req_id).append(&new_creds).append_list(&bodies); @@ -416,7 +426,7 @@ fn get_block_receipts() { let response = { assert_eq!(receipts.len(), 10); - let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()); + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); let mut response_stream = RlpStream::new_list(3); response_stream.append(&req_id).append(&new_creds).append_list(&receipts); @@ -475,7 +485,7 @@ fn get_state_proofs() { }).unwrap()), ]; - let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()); + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); let mut response_stream = RlpStream::new_list(3); response_stream.append(&req_id).append(&new_creds).append_list(&responses); @@ -517,7 +527,7 @@ fn get_contract_code() { code: key1.iter().chain(key2.iter()).cloned().collect(), })]; - let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()); + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); let mut response_stream = RlpStream::new_list(3); @@ -558,9 +568,8 @@ fn epoch_signal() { })]; let limit = *flow_params.limit(); - let cost = flow_params.compute_cost_multi(requests.requests()); + let cost = flow_params.compute_cost_multi(requests.requests()).unwrap(); - println!("limit = {}, cost = {}", limit, cost); let new_creds = limit - cost; let mut response_stream = RlpStream::new_list(3); @@ -605,9 +614,8 @@ fn proof_of_execution() { let response = { let limit = *flow_params.limit(); - let cost = flow_params.compute_cost_multi(requests.requests()); + let cost = flow_params.compute_cost_multi(requests.requests()).unwrap(); - println!("limit = {}, cost = {}", limit, cost); let new_creds = limit - cost; let mut response_stream = RlpStream::new_list(3); @@ -713,3 +721,46 @@ fn id_guard() { assert_eq!(peer_info.failed_requests, &[req_id_1]); } } + +#[test] +fn get_transaction_index() { + let capabilities = capabilities(); + + let (provider, proto) = setup(capabilities.clone()); + let flow_params = proto.flow_params.read().clone(); + + let cur_status = status(provider.client.chain_info()); + + { + let packet_body = write_handshake(&cur_status, &capabilities, &proto); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); + } + + let req_id = 112; + let key1: H256 = U256::from(11223344).into(); + + let request = Request::TransactionIndex(IncompleteTransactionIndexRequest { + hash: key1.into(), + }); + + let requests = encode_single(request.clone()); + let request_body = make_packet(req_id, &requests); + let response = { + let response = vec![Response::TransactionIndex(TransactionIndexResponse { + num: 100, + hash: key1, + index: 55, + })]; + + let new_creds = *flow_params.limit() - flow_params.compute_cost_multi(requests.requests()).unwrap(); + + let mut response_stream = RlpStream::new_list(3); + + response_stream.append(&req_id).append(&new_creds).append_list(&response); + response_stream.out() + }; + + let expected = Expect::Respond(packet::RESPONSE, response); + proto.handle_packet(&expected, &1, packet::REQUEST, &request_body); +} diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index 40da12348..c7d2a01e1 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -195,6 +195,7 @@ fn guess_capabilities(requests: &[CheckedRequest]) -> Capabilities { caps.serve_headers = true, CheckedRequest::HeaderByHash(_, _) => caps.serve_headers = true, + CheckedRequest::TransactionIndex(_, _) => {} // hashes yield no info. CheckedRequest::Signal(_, _) => caps.serve_headers = true, CheckedRequest::Body(ref req, _) => if let Ok(ref hdr) = req.0.as_ref() { diff --git a/ethcore/light/src/on_demand/request.rs b/ethcore/light/src/on_demand/request.rs index 4b0da5677..2c0dd4121 100644 --- a/ethcore/light/src/on_demand/request.rs +++ b/ethcore/light/src/on_demand/request.rs @@ -48,6 +48,8 @@ pub enum Request { HeaderProof(HeaderProof), /// A request for a header by hash. HeaderByHash(HeaderByHash), + /// A request for the index of a transaction. + TransactionIndex(TransactionIndex), /// A request for block receipts. Receipts(BlockReceipts), /// A request for a block body. @@ -135,6 +137,7 @@ macro_rules! impl_single { // implement traits for each kind of request. impl_single!(HeaderProof, HeaderProof, (H256, U256)); impl_single!(HeaderByHash, HeaderByHash, encoded::Header); +impl_single!(TransactionIndex, TransactionIndex, net_request::TransactionIndexResponse); impl_single!(Receipts, BlockReceipts, Vec); impl_single!(Body, Body, encoded::Block); impl_single!(Account, Account, Option); @@ -244,6 +247,7 @@ impl From for HeaderRef { pub enum CheckedRequest { HeaderProof(HeaderProof, net_request::IncompleteHeaderProofRequest), HeaderByHash(HeaderByHash, net_request::IncompleteHeadersRequest), + TransactionIndex(TransactionIndex, net_request::IncompleteTransactionIndexRequest), Receipts(BlockReceipts, net_request::IncompleteReceiptsRequest), Body(Body, net_request::IncompleteBodyRequest), Account(Account, net_request::IncompleteAccountRequest), @@ -270,6 +274,12 @@ impl From for CheckedRequest { }; CheckedRequest::HeaderProof(req, net_req) } + Request::TransactionIndex(req) => { + let net_req = net_request::IncompleteTransactionIndexRequest { + hash: req.0.clone(), + }; + CheckedRequest::TransactionIndex(req, net_req) + } Request::Body(req) => { let net_req = net_request::IncompleteBodyRequest { hash: req.0.field(), @@ -326,6 +336,7 @@ impl CheckedRequest { match self { CheckedRequest::HeaderProof(_, req) => NetRequest::HeaderProof(req), CheckedRequest::HeaderByHash(_, req) => NetRequest::Headers(req), + CheckedRequest::TransactionIndex(_, req) => NetRequest::TransactionIndex(req), CheckedRequest::Receipts(_, req) => NetRequest::Receipts(req), CheckedRequest::Body(_, req) => NetRequest::Body(req), CheckedRequest::Account(_, req) => NetRequest::Account(req), @@ -454,6 +465,7 @@ macro_rules! match_me { match $me { CheckedRequest::HeaderProof($check, $req) => $e, CheckedRequest::HeaderByHash($check, $req) => $e, + CheckedRequest::TransactionIndex($check, $req) => $e, CheckedRequest::Receipts($check, $req) => $e, CheckedRequest::Body($check, $req) => $e, CheckedRequest::Account($check, $req) => $e, @@ -482,6 +494,7 @@ impl IncompleteRequest for CheckedRequest { _ => Ok(()), } } + CheckedRequest::TransactionIndex(_, ref req) => req.check_outputs(f), CheckedRequest::Receipts(_, ref req) => req.check_outputs(f), CheckedRequest::Body(_, ref req) => req.check_outputs(f), CheckedRequest::Account(_, ref req) => req.check_outputs(f), @@ -503,6 +516,7 @@ impl IncompleteRequest for CheckedRequest { match self { CheckedRequest::HeaderProof(_, req) => req.complete().map(CompleteRequest::HeaderProof), CheckedRequest::HeaderByHash(_, req) => req.complete().map(CompleteRequest::Headers), + CheckedRequest::TransactionIndex(_, req) => req.complete().map(CompleteRequest::TransactionIndex), CheckedRequest::Receipts(_, req) => req.complete().map(CompleteRequest::Receipts), CheckedRequest::Body(_, req) => req.complete().map(CompleteRequest::Body), CheckedRequest::Account(_, req) => req.complete().map(CompleteRequest::Account), @@ -545,6 +559,9 @@ impl net_request::CheckedRequest for CheckedRequest { CheckedRequest::HeaderByHash(ref prover, _) => expect!((&NetResponse::Headers(ref res), &CompleteRequest::Headers(ref req)) => prover.check_response(cache, &req.start, &res.headers).map(Response::HeaderByHash)), + CheckedRequest::TransactionIndex(ref prover, _) => + expect!((&NetResponse::TransactionIndex(ref res), _) => + prover.check_response(cache, res).map(Response::TransactionIndex)), CheckedRequest::Receipts(ref prover, _) => expect!((&NetResponse::Receipts(ref res), _) => prover.check_response(cache, &res.receipts).map(Response::Receipts)), @@ -575,6 +592,8 @@ pub enum Response { HeaderProof((H256, U256)), /// Response to a header-by-hash request. HeaderByHash(encoded::Header), + /// Response to a transaction-index request. + TransactionIndex(net_request::TransactionIndexResponse), /// Response to a receipts request. Receipts(Vec), /// Response to a block body request. @@ -723,6 +742,33 @@ impl HeaderByHash { } } +/// Request for a transaction index. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TransactionIndex(pub Field); + +impl TransactionIndex { + /// Check a response for the transaction index. + // + // TODO: proper checking involves looking at canonicality of the + // hash w.r.t. the current best block header. + // + // unlike all other forms of request, we don't know the header to check + // until we make this request. + // + // This would require lookups in the database or perhaps CHT requests, + // which aren't currently possible. + // + // Also, returning a result that is not locally canonical doesn't necessarily + // indicate misbehavior, so the punishment scheme would need to be revised. + pub fn check_response( + &self, + _cache: &Mutex<::cache::Cache>, + res: &net_request::TransactionIndexResponse, + ) -> Result { + Ok(res.clone()) + } +} + /// Request for a block, with header for verification. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Body(pub HeaderRef); diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index d71a5fff0..3e2ddcb9d 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! A provider for the LES protocol. This is typically a full node, who can +//! A provider for the PIP protocol. This is typically a full node, who can //! give as much data as necessary to its peers. use std::sync::Arc; @@ -102,6 +102,10 @@ pub trait Provider: Send + Sync { /// Get a block header by id. fn block_header(&self, id: BlockId) -> Option; + /// Get a transaction index by hash. + fn transaction_index(&self, req: request::CompleteTransactionIndexRequest) + -> Option; + /// Fulfill a block body request. fn block_body(&self, req: request::CompleteBodyRequest) -> Option; @@ -150,6 +154,18 @@ impl Provider for T { BlockChainClient::block_header(self, id) } + fn transaction_index(&self, req: request::CompleteTransactionIndexRequest) + -> Option + { + use ethcore::ids::TransactionId; + + self.transaction_receipt(TransactionId::Hash(req.hash)).map(|receipt| request::TransactionIndexResponse { + num: receipt.block_number, + hash: receipt.block_hash, + index: receipt.transaction_index as u64, + }) + } + fn block_body(&self, req: request::CompleteBodyRequest) -> Option { BlockChainClient::block_body(self, BlockId::Hash(req.hash)) .map(|body| ::request::BodyResponse { body: body }) @@ -311,6 +327,12 @@ impl Provider for LightProvider { self.client.as_light_client().block_header(id) } + fn transaction_index(&self, _req: request::CompleteTransactionIndexRequest) + -> Option + { + None + } + fn block_body(&self, _req: request::CompleteBodyRequest) -> Option { None } diff --git a/rpc/src/v1/helpers/light_fetch.rs b/rpc/src/v1/helpers/light_fetch.rs index bb030b46a..ac5902b51 100644 --- a/rpc/src/v1/helpers/light_fetch.rs +++ b/rpc/src/v1/helpers/light_fetch.rs @@ -23,7 +23,8 @@ use ethcore::encoded; use ethcore::executed::{Executed, ExecutionError}; use ethcore::ids::BlockId; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::transaction::{Action, Transaction as EthTransaction}; +use ethcore::transaction::{Action, Transaction as EthTransaction, SignedTransaction}; +use ethcore::receipt::Receipt; use jsonrpc_core::{BoxFuture, Error}; use jsonrpc_core::futures::{future, Future}; @@ -38,14 +39,18 @@ use light::request::Field; use ethsync::LightSync; use bigint::prelude::U256; +use hash::H256; use util::Address; use parking_lot::Mutex; use v1::helpers::{CallRequest as CallRequestHelper, errors, dispatch}; -use v1::types::{BlockNumber, CallRequest, Log}; +use v1::types::{BlockNumber, CallRequest, Log, Transaction}; + +const NO_INVALID_BACK_REFS: &'static str = "Fails only on invalid back-references; back-references here known to be valid; qed"; /// Helper for fetching blockchain data either from the light client or the network /// as necessary. +#[derive(Clone)] pub struct LightFetch { /// The light client. pub client: Arc, @@ -57,6 +62,19 @@ pub struct LightFetch { pub cache: Arc>, } +/// Extract a transaction at given index. +pub fn extract_transaction_at_index(block: encoded::Block, index: usize, eip86_transition: u64) -> Option { + block.transactions().into_iter().nth(index) + .and_then(|tx| SignedTransaction::new(tx).ok()) + .map(|tx| Transaction::from_signed(tx, block.number(), eip86_transition)) + .map(|mut tx| { + tx.block_hash = Some(block.hash().into()); + tx.transaction_index = Some(index.into()); + tx + }) +} + + /// Type alias for convenience. pub type ExecutionResult = Result; @@ -131,7 +149,7 @@ impl LightFetch { } } - /// helper for getting account info at a given block. + /// Helper for getting account info at a given block. /// `None` indicates the account doesn't exist at the given block. pub fn account(&self, address: Address, id: BlockId) -> BoxFuture, Error> { let mut reqs = Vec::new(); @@ -158,7 +176,7 @@ impl LightFetch { } } - /// helper for getting proved execution. + /// Helper for getting proved execution. pub fn proved_execution(&self, req: CallRequest, num: Trailing) -> BoxFuture { const DEFAULT_GAS_PRICE: u64 = 21_000; // starting gas when gas not provided. @@ -235,7 +253,7 @@ impl LightFetch { })) } - /// get a block itself. fails on unknown block ID. + /// Get a block itself. Fails on unknown block ID. pub fn block(&self, id: BlockId) -> BoxFuture { let mut reqs = Vec::new(); let header_ref = match self.make_header_requests(id, &mut reqs) { @@ -247,7 +265,7 @@ impl LightFetch { let maybe_future = self.sync.with_context(move |ctx| { Box::new(self.on_demand.request_raw(ctx, reqs) - .expect("all back-references known to be valid; qed") + .expect(NO_INVALID_BACK_REFS) .map(|mut res| match res.pop() { Some(OnDemandResponse::Body(b)) => b, _ => panic!("responses correspond directly with requests in amount and type; qed"), @@ -261,13 +279,37 @@ impl LightFetch { } } - /// get transaction logs + /// Get the block receipts. Fails on unknown block ID. + pub fn receipts(&self, id: BlockId) -> BoxFuture, Error> { + let mut reqs = Vec::new(); + let header_ref = match self.make_header_requests(id, &mut reqs) { + Ok(r) => r, + Err(e) => return Box::new(future::err(e)), + }; + + reqs.push(request::BlockReceipts(header_ref).into()); + + let maybe_future = self.sync.with_context(move |ctx| { + Box::new(self.on_demand.request_raw(ctx, reqs) + .expect(NO_INVALID_BACK_REFS) + .map(|mut res| match res.pop() { + Some(OnDemandResponse::Receipts(b)) => b, + _ => panic!("responses correspond directly with requests in amount and type; qed"), + }) + .map_err(errors::on_demand_cancel)) + }); + + match maybe_future { + Some(recv) => recv, + None => Box::new(future::err(errors::network_disabled())) + } + } + + /// Get transaction logs pub fn logs(&self, filter: EthcoreFilter) -> BoxFuture, Error> { use std::collections::BTreeMap; use jsonrpc_core::futures::stream::{self, Stream}; - const NO_INVALID_BACK_REFS: &'static str = "Fails only on invalid back-references; back-references here known to be valid; qed"; - // early exit for "to" block before "from" block. let best_number = self.client.chain_info().best_block_number; let block_number = |id| match id { @@ -318,6 +360,65 @@ impl LightFetch { None => Box::new(future::err(errors::network_disabled())), } } + + // Get a transaction by hash. also returns the index in the block. + // Only returns transactions in the canonical chain. + pub fn transaction_by_hash(&self, tx_hash: H256, eip86_transition: u64) + -> BoxFuture, Error> + { + let params = (self.sync.clone(), self.on_demand.clone()); + let fetcher: Self = self.clone(); + + Box::new(future::loop_fn(params, move |(sync, on_demand)| { + let maybe_future = sync.with_context(|ctx| { + let req = request::TransactionIndex(tx_hash.clone().into()); + on_demand.request(ctx, req) + }); + + let eventual_index = match maybe_future { + Some(e) => e.expect(NO_INVALID_BACK_REFS).map_err(errors::on_demand_cancel), + None => return Either::A(future::err(errors::network_disabled())), + }; + + let fetcher = fetcher.clone(); + let extract_transaction = eventual_index.and_then(move |index| { + // check that the block is known by number. + // that ensures that it is within the chain that we are aware of. + fetcher.block(BlockId::Number(index.num)).then(move |blk| match blk { + Ok(blk) => { + // if the block is known by number, make sure the + // index from earlier isn't garbage. + + if blk.hash() != index.hash { + // index is on a different chain from us. + return Ok(future::Loop::Continue((sync, on_demand))) + } + + let index = index.index as usize; + let transaction = extract_transaction_at_index(blk, index, eip86_transition); + + if transaction.as_ref().map_or(true, |tx| tx.hash != tx_hash.into()) { + // index is actively wrong: indicated block has + // fewer transactions than necessary or the transaction + // at that index had a different hash. + // TODO: punish peer/move into OnDemand somehow? + Ok(future::Loop::Continue((sync, on_demand))) + } else { + let transaction = transaction.map(move |tx| (tx, index)); + Ok(future::Loop::Break(transaction)) + } + } + Err(ref e) if e == &errors::unknown_block() => { + // block by number not in the canonical chain. + Ok(future::Loop::Break(None)) + } + Err(e) => Err(e), + }) + }); + + Either::B(extract_transaction) + })) + } } #[derive(Clone)] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 3afdf2d74..7c16c5a8a 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -458,38 +458,54 @@ impl Eth for EthClient where Box::new(future::done(self.block(num.into(), include_txs))) } - fn transaction_by_hash(&self, hash: RpcH256) -> Result, Error> { + fn transaction_by_hash(&self, hash: RpcH256) -> BoxFuture, Error> { let hash: H256 = hash.into(); let block_number = self.client.chain_info().best_block_number; - Ok(self.transaction(TransactionId::Hash(hash))?.or_else(|| self.miner.transaction(block_number, &hash).map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)))) + let tx = try_bf!(self.transaction(TransactionId::Hash(hash))).or_else(|| { + self.miner.transaction(block_number, &hash) + .map(|t| Transaction::from_pending(t, block_number, self.eip86_transition)) + }); + + Box::new(future::ok(tx)) } - fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { - self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value())) + fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> BoxFuture, Error> { + Box::new(future::done( + self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value())) + )) } - fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { - self.transaction(TransactionId::Location(num.into(), index.value())) + fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> BoxFuture, Error> { + Box::new(future::done( + self.transaction(TransactionId::Location(num.into(), index.value())) + )) } - fn transaction_receipt(&self, hash: RpcH256) -> Result, Error> { + fn transaction_receipt(&self, hash: RpcH256) -> BoxFuture, Error> { let best_block = self.client.chain_info().best_block_number; let hash: H256 = hash.into(); + match (self.miner.pending_receipt(best_block, &hash), self.options.allow_pending_receipt_query) { - (Some(receipt), true) => Ok(Some(receipt.into())), + (Some(receipt), true) => Box::new(future::ok(Some(receipt.into()))), _ => { let receipt = self.client.transaction_receipt(TransactionId::Hash(hash)); - Ok(receipt.map(Into::into)) + Box::new(future::ok(receipt.map(Into::into))) } } } - fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { - self.uncle(UncleId { block: BlockId::Hash(hash.into()), position: index.value() }) + fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> BoxFuture, Error> { + Box::new(future::done(self.uncle(UncleId { + block: BlockId::Hash(hash.into()), + position: index.value() + }))) } - fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { - self.uncle(UncleId { block: num.into(), position: index.value() }) + fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> BoxFuture, Error> { + Box::new(future::done(self.uncle(UncleId { + block: num.into(), + position: index.value() + }))) } fn compilers(&self) -> Result, Error> { diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 0f7438eb0..b797e76c2 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -39,11 +39,10 @@ use hash::{KECCAK_NULL_RLP, KECCAK_EMPTY_LIST_RLP}; use bigint::prelude::U256; use parking_lot::{RwLock, Mutex}; - use v1::impls::eth_filter::Filterable; use v1::helpers::{errors, limit_logs}; use v1::helpers::{PollFilter, PollManager}; -use v1::helpers::light_fetch::LightFetch; +use v1::helpers::light_fetch::{self, LightFetch}; use v1::traits::Eth; use v1::types::{ RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, @@ -80,7 +79,6 @@ impl Clone for EthClient { } } - impl EthClient { /// Create a new `EthClient` with a handle to the light sync instance, client, /// and on-demand request service, which is assumed to be attached as a handler. @@ -393,33 +391,72 @@ impl Eth for EthClient { })) } - fn transaction_by_hash(&self, _hash: RpcH256) -> Result, Error> { - Err(errors::unimplemented(None)) + fn transaction_by_hash(&self, hash: RpcH256) -> BoxFuture, Error> { + let eip86 = self.client.eip86_transition(); + Box::new(self.fetcher().transaction_by_hash(hash.into(), eip86).map(|x| x.map(|(tx, _)| tx))) } - fn transaction_by_block_hash_and_index(&self, _hash: RpcH256, _idx: Index) -> Result, Error> { - Err(errors::unimplemented(None)) + fn transaction_by_block_hash_and_index(&self, hash: RpcH256, idx: Index) -> BoxFuture, Error> { + let eip86 = self.client.eip86_transition(); + Box::new(self.fetcher().block(BlockId::Hash(hash.into())).map(move |block| { + light_fetch::extract_transaction_at_index(block, idx.value(), eip86) + })) } - fn transaction_by_block_number_and_index(&self, _num: BlockNumber, _idx: Index) -> Result, Error> { - Err(errors::unimplemented(None)) + fn transaction_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture, Error> { + let eip86 = self.client.eip86_transition(); + Box::new(self.fetcher().block(num.into()).map(move |block| { + light_fetch::extract_transaction_at_index(block, idx.value(), eip86) + })) } - fn transaction_receipt(&self, _hash: RpcH256) -> Result, Error> { - Err(errors::unimplemented(None)) + fn transaction_receipt(&self, hash: RpcH256) -> BoxFuture, Error> { + let eip86 = self.client.eip86_transition(); + let fetcher = self.fetcher(); + Box::new(fetcher.transaction_by_hash(hash.clone().into(), eip86).and_then(move |tx| { + // the block hash included in the transaction object here has + // already been checked for canonicality and whether it contains + // the transaction. + match tx { + Some((tx, index)) => match tx.block_hash.clone() { + Some(block_hash) => { + let extract_receipt = fetcher.receipts(BlockId::Hash(block_hash.clone().into())) + .and_then(move |mut receipts| future::ok(receipts.swap_remove(index))) + .map(Receipt::from) + .map(move |mut receipt| { + receipt.transaction_hash = Some(hash); + receipt.transaction_index = Some(index.into()); + receipt.block_hash = Some(block_hash); + receipt.block_number = tx.block_number; + receipt + }) + .map(Some); + + Either::B(extract_receipt) + } + None => Either::A(future::err(errors::unknown_block())), + }, + None => Either::A(future::ok(None)), + } + })) } - fn uncle_by_block_hash_and_index(&self, _hash: RpcH256, _idx: Index) -> Result, Error> { - Err(errors::unimplemented(None)) + fn uncle_by_block_hash_and_index(&self, hash: RpcH256, idx: Index) -> BoxFuture, Error> { + let client = self.client.clone(); + Box::new(self.fetcher().block(BlockId::Hash(hash.into())).map(move |block| { + extract_uncle_at_index(block, idx, client) + })) } - fn uncle_by_block_number_and_index(&self, _num: BlockNumber, _idx: Index) -> Result, Error> { - Err(errors::unimplemented(None)) + fn uncle_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture, Error> { + let client = self.client.clone(); + Box::new(self.fetcher().block(num.into()).map(move |block| { + extract_uncle_at_index(block, idx, client) + })) } fn compilers(&self) -> Result, Error> { Err(errors::deprecated("Compilation functionality is deprecated.".to_string())) - } fn compile_lll(&self, _: String) -> Result { @@ -478,3 +515,37 @@ impl Filterable for EthClient { &self.polls } } + +fn extract_uncle_at_index(block: encoded::Block, index: Index, client: Arc) -> Option { + let uncle = match block.uncles().into_iter().nth(index.value()) { + Some(u) => u, + None => return None, + }; + + let extra_info = client.engine().extra_info(&uncle); + Some(RichBlock { + inner: Block { + hash: Some(uncle.hash().into()), + size: None, + parent_hash: uncle.parent_hash().clone().into(), + uncles_hash: uncle.uncles_hash().clone().into(), + author: uncle.author().clone().into(), + miner: uncle.author().clone().into(), + state_root: uncle.state_root().clone().into(), + transactions_root: uncle.transactions_root().clone().into(), + number: Some(uncle.number().into()), + gas_used: uncle.gas_used().clone().into(), + gas_limit: uncle.gas_limit().clone().into(), + logs_bloom: uncle.log_bloom().clone().into(), + timestamp: uncle.timestamp().into(), + difficulty: uncle.difficulty().clone().into(), + total_difficulty: None, + receipts_root: uncle.receipts_root().clone().into(), + extra_data: uncle.extra_data().clone().into(), + seal_fields: uncle.seal().into_iter().cloned().map(Into::into).collect(), + uncles: vec![], + transactions: BlockTransactions::Hashes(vec![]), + }, + extra_info: extra_info, + }) +} diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 8bd97108c..96329d18c 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -547,7 +547,7 @@ fn rpc_eth_pending_transaction_by_hash() { tester.miner.pending_transactions.lock().insert(H256::zero(), tx); } - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"chainId":null,"condition":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":"0x0","chainId":null,"condition":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; let request = r#"{ "jsonrpc": "2.0", "method": "eth_getTransactionByHash", @@ -863,7 +863,7 @@ fn rpc_eth_sign_transaction() { let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + - r#""blockHash":null,"blockNumber":null,"# + + r#""blockHash":null,"blockNumber":"0x0","# + &format!("\"chainId\":{},", t.chain_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""condition":null,"creates":null,"# + &format!("\"from\":\"0x{:?}\",", &address) + diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index ed27862ac..1653a1908 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -234,7 +234,7 @@ fn rpc_parity_remove_transaction() { let hash = signed.hash(); let request = r#"{"jsonrpc": "2.0", "method": "parity_removeTransaction", "params":[""#.to_owned() + &format!("0x{:?}", hash) + r#""], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"chainId":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0xa2e0da8a8064e0b9f93e95a53c2db6d01280efb8ac72a708d25487e67dd0f8fc","input":"0x","nonce":"0x1","publicKey":null,"r":"0x1","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80800101","s":"0x1","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":"0x0","chainId":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0xa2e0da8a8064e0b9f93e95a53c2db6d01280efb8ac72a708d25487e67dd0f8fc","input":"0x","nonce":"0x1","publicKey":null,"r":"0x1","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80800101","s":"0x1","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#; miner.pending_transactions.lock().insert(hash, signed); assert_eq!(io.handle_request_sync(&request), Some(response.to_owned())); diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index af84d336a..95095cb72 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -456,7 +456,7 @@ fn should_confirm_sign_transaction_with_rlp() { let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + - r#""blockHash":null,"blockNumber":null,"# + + r#""blockHash":null,"blockNumber":"0x0","# + &format!("\"chainId\":{},", t.chain_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""condition":null,"creates":null,"# + &format!("\"from\":\"0x{:?}\",", &address) + diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index fd08f5ee9..a767bfb77 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -299,7 +299,7 @@ fn should_add_sign_transaction_to_the_queue() { let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + - r#""blockHash":null,"blockNumber":null,"# + + r#""blockHash":null,"blockNumber":"0x0","# + &format!("\"chainId\":{},", t.chain_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""condition":null,"creates":null,"# + &format!("\"from\":\"0x{:?}\",", &address) + diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 83543f10b..6c052bf31 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -117,27 +117,27 @@ build_rpc_trait! { /// Get transaction by its hash. #[rpc(name = "eth_getTransactionByHash")] - fn transaction_by_hash(&self, H256) -> Result, Error>; + fn transaction_by_hash(&self, H256) -> BoxFuture, Error>; /// Returns transaction at given block hash and index. #[rpc(name = "eth_getTransactionByBlockHashAndIndex")] - fn transaction_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; + fn transaction_by_block_hash_and_index(&self, H256, Index) -> BoxFuture, Error>; /// Returns transaction by given block number and index. #[rpc(name = "eth_getTransactionByBlockNumberAndIndex")] - fn transaction_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; + fn transaction_by_block_number_and_index(&self, BlockNumber, Index) -> BoxFuture, Error>; - /// Returns transaction receipt. + /// Returns transaction receipt by transaction hash. #[rpc(name = "eth_getTransactionReceipt")] - fn transaction_receipt(&self, H256) -> Result, Error>; + fn transaction_receipt(&self, H256) -> BoxFuture, Error>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockHashAndIndex")] - fn uncle_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; + fn uncle_by_block_hash_and_index(&self, H256, Index) -> BoxFuture, Error>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockNumberAndIndex")] - fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; + fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> BoxFuture, Error>; /// Returns available compilers. /// @deprecated diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index 90d512c86..570c21120 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -213,7 +213,7 @@ impl Transaction { hash: t.hash().into(), nonce: t.nonce.into(), block_hash: None, - block_number: None, + block_number: Some(block_number.into()), transaction_index: None, from: t.sender().into(), to: match t.action {