From 5eba9078fcc41806748fee9ed39b2240e8c1d807 Mon Sep 17 00:00:00 2001 From: Vurich Date: Fri, 30 Jun 2017 10:58:48 +0200 Subject: [PATCH 1/3] Report whether a peer was kept from `Handler::on_connect` --- ethcore/light/src/net/error.rs | 4 ++ ethcore/light/src/net/mod.rs | 67 ++++++++++++++++++++++++++++-- ethcore/light/src/on_demand/mod.rs | 25 +++++++++-- sync/src/light_sync/mod.rs | 47 ++++++++++++--------- 4 files changed, 117 insertions(+), 26 deletions(-) diff --git a/ethcore/light/src/net/error.rs b/ethcore/light/src/net/error.rs index 1c0374c7e..578978348 100644 --- a/ethcore/light/src/net/error.rs +++ b/ethcore/light/src/net/error.rs @@ -66,6 +66,8 @@ pub enum Error { BadProtocolVersion, /// Peer is overburdened. Overburdened, + /// No handler kept the peer. + RejectedByHandlers, } impl Error { @@ -85,6 +87,7 @@ impl Error { Error::UnsupportedProtocolVersion(_) => Punishment::Disable, Error::BadProtocolVersion => Punishment::Disable, Error::Overburdened => Punishment::None, + Error::RejectedByHandlers => Punishment::Disconnect, } } } @@ -117,6 +120,7 @@ impl fmt::Display for Error { Error::UnsupportedProtocolVersion(pv) => write!(f, "Unsupported protocol version: {}", pv), Error::BadProtocolVersion => write!(f, "Bad protocol version in handshake"), Error::Overburdened => write!(f, "Peer overburdened"), + Error::RejectedByHandlers => write!(f, "No handler kept this peer"), } } } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 6ab5903df..24f50e5d5 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -31,6 +31,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::ops::{BitOr, BitAnd, Not}; use provider::Provider; use request::{Request, NetworkRequests as Requests, Response}; @@ -157,6 +158,54 @@ pub struct Peer { awaiting_acknowledge: Option<(SteadyTime, Arc)>, } +/// Whether or not a peer was kept by a handler +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PeerStatus { + /// The peer was kept + Kept, + /// The peer was not kept + Unkept, +} + +impl Not for PeerStatus { + type Output = Self; + + fn not(self) -> Self { + use self::PeerStatus::*; + + match self { + Kept => Unkept, + Unkept => Kept, + } + } +} + +impl BitAnd for PeerStatus { + type Output = Self; + + fn bitand(self, other: Self) -> Self { + use self::PeerStatus::*; + + match (self, other) { + (Kept, Kept) => Kept, + _ => Unkept, + } + } +} + +impl BitOr for PeerStatus { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + use self::PeerStatus::*; + + match (self, other) { + (_, Kept) | (Kept, _) => Kept, + _ => Unkept, + } + } +} + /// A light protocol event handler. /// /// Each handler function takes a context which describes the relevant peer @@ -168,7 +217,12 @@ pub struct Peer { /// that relevant data will be stored by interested handlers. pub trait Handler: Send + Sync { /// Called when a peer connects. - fn on_connect(&self, _ctx: &EventContext, _status: &Status, _capabilities: &Capabilities) { } + fn on_connect( + &self, + _ctx: &EventContext, + _status: &Status, + _capabilities: &Capabilities + ) -> PeerStatus { PeerStatus::Kept } /// Called when a peer disconnects, with a list of unfulfilled request IDs as /// of yet. fn on_disconnect(&self, _ctx: &EventContext, _unfulfilled: &[ReqId]) { } @@ -766,15 +820,20 @@ impl LightProtocol { awaiting_acknowledge: None, })); - for handler in &self.handlers { + let any_kept = self.handlers.iter().map( + |handler| handler.on_connect(&Ctx { peer: *peer, io: io, proto: self, }, &status, &capabilities) - } + ).fold(PeerStatus::Kept, PeerStatus::bitor); - Ok(()) + if any_kept == PeerStatus::Unkept { + Err(Error::RejectedByHandlers) + } else { + Ok(()) + } } // Handle an announcement. diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index 435c72cf7..8306f3a1a 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -29,7 +29,17 @@ use futures::sync::oneshot::{self, Sender, Receiver, Canceled}; use network::PeerId; use util::{RwLock, Mutex}; -use net::{self, Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId}; +use net::{ + self, + Handler, + PeerStatus, + Status, + Capabilities, + Announcement, + EventContext, + BasicContext, + ReqId, +}; use cache::Cache; use request::{self as basic_request, Request as NetworkRequest}; use self::request::CheckedRequest; @@ -402,9 +412,18 @@ impl OnDemand { } impl Handler for OnDemand { - fn on_connect(&self, ctx: &EventContext, status: &Status, capabilities: &Capabilities) { - self.peers.write().insert(ctx.peer(), Peer { status: status.clone(), capabilities: capabilities.clone() }); + fn on_connect( + &self, + ctx: &EventContext, + status: &Status, + capabilities: &Capabilities + ) -> PeerStatus { + self.peers.write().insert( + ctx.peer(), + Peer { status: status.clone(), capabilities: capabilities.clone() } + ); self.attempt_dispatch(ctx.as_basic()); + PeerStatus::Kept } fn on_disconnect(&self, ctx: &EventContext, unfulfilled: &[ReqId]) { diff --git a/sync/src/light_sync/mod.rs b/sync/src/light_sync/mod.rs index 2cfbafa17..558e28ecd 100644 --- a/sync/src/light_sync/mod.rs +++ b/sync/src/light_sync/mod.rs @@ -39,8 +39,9 @@ use std::sync::Arc; use ethcore::encoded; use light::client::{AsLightClient, LightChainClient}; use light::net::{ - Announcement, Handler, BasicContext, EventContext, - Capabilities, ReqId, Status, Error as NetError, + PeerStatus, Announcement, Handler, BasicContext, + EventContext, Capabilities, ReqId, Status, + Error as NetError, }; use light::request::{self, CompleteHeadersRequest as HeadersRequest}; use network::PeerId; @@ -229,26 +230,34 @@ pub struct LightSync { } impl Handler for LightSync { - fn on_connect(&self, ctx: &EventContext, status: &Status, capabilities: &Capabilities) { - if !capabilities.serve_headers { + fn on_connect( + &self, + ctx: &EventContext, + status: &Status, + capabilities: &Capabilities + ) -> PeerStatus { + if capabilities.serve_headers { + let chain_info = ChainInfo { + head_td: status.head_td, + head_hash: status.head_hash, + head_num: status.head_num, + }; + + { + let mut best = self.best_seen.lock(); + *best = ::std::cmp::max(best.clone(), Some(chain_info.clone())); + } + + self.peers.write().insert(ctx.peer(), Mutex::new(Peer::new(chain_info))); + self.maintain_sync(ctx.as_basic()); + + PeerStatus::Kept + } else { trace!(target: "sync", "Disconnecting irrelevant peer: {}", ctx.peer()); ctx.disconnect_peer(ctx.peer()); - return; + + PeerStatus::Unkept } - - let chain_info = ChainInfo { - head_td: status.head_td, - head_hash: status.head_hash, - head_num: status.head_num, - }; - - { - let mut best = self.best_seen.lock(); - *best = ::std::cmp::max(best.clone(), Some(chain_info.clone())); - } - - self.peers.write().insert(ctx.peer(), Mutex::new(Peer::new(chain_info))); - self.maintain_sync(ctx.as_basic()); } fn on_disconnect(&self, ctx: &EventContext, unfulfilled: &[ReqId]) { From 17de29e69a92d14641f635f9e4d9b09ce404f57a Mon Sep 17 00:00:00 2001 From: Vurich Date: Fri, 30 Jun 2017 12:10:12 +0200 Subject: [PATCH 2/3] Prevent disconnect from within handler (and style cleanup) --- ethcore/light/src/net/mod.rs | 15 +++++++++------ ethcore/light/src/on_demand/mod.rs | 11 ++--------- sync/src/light_sync/mod.rs | 7 +++---- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 24f50e5d5..5af921d80 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -821,12 +821,15 @@ impl LightProtocol { })); let any_kept = self.handlers.iter().map( - |handler| - handler.on_connect(&Ctx { - peer: *peer, - io: io, - proto: self, - }, &status, &capabilities) + |handler| handler.on_connect( + &Ctx { + peer: *peer, + io: io, + proto: self, + }, + &status, + &capabilities + ) ).fold(PeerStatus::Kept, PeerStatus::bitor); if any_kept == PeerStatus::Unkept { diff --git a/ethcore/light/src/on_demand/mod.rs b/ethcore/light/src/on_demand/mod.rs index 8306f3a1a..c1919ebd1 100644 --- a/ethcore/light/src/on_demand/mod.rs +++ b/ethcore/light/src/on_demand/mod.rs @@ -30,15 +30,8 @@ use network::PeerId; use util::{RwLock, Mutex}; use net::{ - self, - Handler, - PeerStatus, - Status, - Capabilities, - Announcement, - EventContext, - BasicContext, - ReqId, + self, Handler, PeerStatus, Status, Capabilities, + Announcement, EventContext, BasicContext, ReqId, }; use cache::Cache; use request::{self as basic_request, Request as NetworkRequest}; diff --git a/sync/src/light_sync/mod.rs b/sync/src/light_sync/mod.rs index 558e28ecd..512ba7943 100644 --- a/sync/src/light_sync/mod.rs +++ b/sync/src/light_sync/mod.rs @@ -236,6 +236,8 @@ impl Handler for LightSync { status: &Status, capabilities: &Capabilities ) -> PeerStatus { + use std::cmp; + if capabilities.serve_headers { let chain_info = ChainInfo { head_td: status.head_td, @@ -245,7 +247,7 @@ impl Handler for LightSync { { let mut best = self.best_seen.lock(); - *best = ::std::cmp::max(best.clone(), Some(chain_info.clone())); + *best = cmp::max(best.clone(), Some(chain_info.clone())); } self.peers.write().insert(ctx.peer(), Mutex::new(Peer::new(chain_info))); @@ -253,9 +255,6 @@ impl Handler for LightSync { PeerStatus::Kept } else { - trace!(target: "sync", "Disconnecting irrelevant peer: {}", ctx.peer()); - ctx.disconnect_peer(ctx.peer()); - PeerStatus::Unkept } } From 826a4ca0a2ba793fffe6a6fa4a78f11bbddf8b2b Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 3 Jul 2017 12:25:10 +0200 Subject: [PATCH 3/3] Implement skeleton for transaction index and epoch transition proof PIP messages (#5908) * add transaction index message without implementing * add epoch proof fetch and response messages --- ethcore/light/src/net/load_timer.rs | 1 + ethcore/light/src/net/mod.rs | 14 +- ethcore/light/src/net/request_credits.rs | 11 +- ethcore/light/src/net/request_set.rs | 1 + ethcore/light/src/types/request/mod.rs | 163 ++++++++++++++++++++++- 5 files changed, 183 insertions(+), 7 deletions(-) diff --git a/ethcore/light/src/net/load_timer.rs b/ethcore/light/src/net/load_timer.rs index 9c6afc7cc..a169b86d9 100644 --- a/ethcore/light/src/net/load_timer.rs +++ b/ethcore/light/src/net/load_timer.rs @@ -55,6 +55,7 @@ fn hardcoded_serve_time(kind: Kind) -> u64 { match kind { Kind::Headers => 500_000, Kind::HeaderProof => 500_000, + Kind::TransactionIndex => 500_000, Kind::Receipts => 1_000_000, Kind::Body => 1_000_000, Kind::Account => 1_500_000, diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 6ab5903df..77c9bc40c 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -80,7 +80,7 @@ pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; pub const MAX_PROTOCOL_VERSION: u8 = 1; /// Packet count for PIP. -pub const PACKET_COUNT: u8 = 5; +pub const PACKET_COUNT: u8 = 9; // packet ID definitions. mod packet { @@ -100,6 +100,10 @@ mod packet { // relay transactions to peers. pub const SEND_TRANSACTIONS: u8 = 0x06; + + // request and respond with epoch transition proof + pub const REQUEST_EPOCH_PROOF: u8 = 0x07; + pub const EPOCH_PROOF: u8 = 0x08; } // timeouts for different kinds of requests. all values are in milliseconds. @@ -110,6 +114,7 @@ mod timeout { // timeouts per request within packet. pub const HEADERS: i64 = 250; // per header? + pub const TRANSACTION_INDEX: i64 = 100; pub const BODY: i64 = 50; pub const RECEIPT: i64 = 50; pub const PROOF: i64 = 100; // state proof @@ -523,6 +528,12 @@ impl LightProtocol { packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), + packet::REQUEST_EPOCH_PROOF | packet::EPOCH_PROOF => { + // ignore these for now, but leave them specified. + debug!(target: "pip", "Ignoring request/response for epoch proof"); + Ok(()) + } + other => { Err(Error::UnrecognizedPacket(other)) } @@ -867,6 +878,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::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 330576f9c..950fa24d8 100644 --- a/ethcore/light/src/net/request_credits.rs +++ b/ethcore/light/src/net/request_credits.rs @@ -83,6 +83,7 @@ impl Credits { pub struct CostTable { base: U256, // cost per packet. headers: U256, // cost per header + transaction_index: U256, body: U256, receipts: U256, account: U256, @@ -98,6 +99,7 @@ impl Default for CostTable { CostTable { base: 100000.into(), headers: 10000.into(), + transaction_index: 10000.into(), body: 15000.into(), receipts: 5000.into(), account: 25000.into(), @@ -119,8 +121,9 @@ impl Encodable for CostTable { s.append(cost); } - s.begin_list(9).append(&self.base); + s.begin_list(10).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); append_cost(s, &self.receipts, request::Kind::Receipts); append_cost(s, &self.account, request::Kind::Account); @@ -136,6 +139,7 @@ impl Decodable for CostTable { let base = rlp.val_at(0)?; let mut headers = None; + let mut transaction_index = None; let mut body = None; let mut receipts = None; let mut account = None; @@ -148,6 +152,7 @@ impl Decodable for CostTable { let cost = cost_list.val_at(1)?; match cost_list.val_at(0)? { request::Kind::Headers => headers = Some(cost), + request::Kind::TransactionIndex => transaction_index = Some(cost), request::Kind::Body => body = Some(cost), request::Kind::Receipts => receipts = Some(cost), request::Kind::Account => account = Some(cost), @@ -163,6 +168,7 @@ impl Decodable for CostTable { Ok(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)?, @@ -224,6 +230,7 @@ impl FlowParams { let costs = CostTable { base: 0.into(), headers: cost_for_kind(Kind::Headers), + transaction_index: cost_for_kind(Kind::TransactionIndex), body: cost_for_kind(Kind::Body), receipts: cost_for_kind(Kind::Receipts), account: cost_for_kind(Kind::Account), @@ -249,6 +256,7 @@ impl FlowParams { costs: CostTable { base: free_cost.clone(), headers: free_cost.clone(), + transaction_index: free_cost.clone(), body: free_cost.clone(), receipts: free_cost.clone(), account: free_cost.clone(), @@ -278,6 +286,7 @@ impl FlowParams { match *request { Request::Headers(ref req) => self.costs.headers * req.max.into(), Request::HeaderProof(_) => self.costs.header_proof, + Request::TransactionIndex(_) => self.costs.transaction_index, Request::Body(_) => self.costs.body, Request::Receipts(_) => self.costs.receipts, Request::Account(_) => self.costs.account, diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs index 1277c2615..35182f0bf 100644 --- a/ethcore/light/src/net/request_set.rs +++ b/ethcore/light/src/net/request_set.rs @@ -132,6 +132,7 @@ fn compute_timeout(reqs: &Requests) -> Duration { tm + match *req { Request::Headers(_) => timeout::HEADERS, Request::HeaderProof(_) => timeout::HEADER_PROOF, + Request::TransactionIndex(_) => timeout::TRANSACTION_INDEX, Request::Receipts(_) => timeout::RECEIPT, Request::Body(_) => timeout::BODY, Request::Account(_) => timeout::PROOF, diff --git a/ethcore/light/src/types/request/mod.rs b/ethcore/light/src/types/request/mod.rs index ceba60dda..ff4a32535 100644 --- a/ethcore/light/src/types/request/mod.rs +++ b/ethcore/light/src/types/request/mod.rs @@ -32,6 +32,11 @@ pub use self::header_proof::{ Incomplete as IncompleteHeaderProofRequest, Response as HeaderProofResponse }; +pub use self::transaction_index::{ + Complete as CompleteTransactionIndexRequest, + Incomplete as IncompleteTransactionIndexRequest, + Response as TransactionIndexResponse +}; pub use self::block_body::{ Complete as CompleteBodyRequest, Incomplete as IncompleteBodyRequest, @@ -242,7 +247,8 @@ pub enum Request { Headers(IncompleteHeadersRequest), /// A request for a header proof (from a CHT) HeaderProof(IncompleteHeaderProofRequest), - // TransactionIndex, + /// A request for a transaction index by hash. + TransactionIndex(IncompleteTransactionIndexRequest), /// A request for a block's receipts. Receipts(IncompleteReceiptsRequest), /// A request for a block body. @@ -264,7 +270,8 @@ pub enum CompleteRequest { Headers(CompleteHeadersRequest), /// A request for a header proof (from a CHT) HeaderProof(CompleteHeaderProofRequest), - // TransactionIndex, + /// A request for a transaction index by hash. + TransactionIndex(CompleteTransactionIndexRequest), /// A request for a block's receipts. Receipts(CompleteReceiptsRequest), /// A request for a block body. @@ -285,6 +292,7 @@ impl CompleteRequest { match *self { CompleteRequest::Headers(_) => Kind::Headers, CompleteRequest::HeaderProof(_) => Kind::HeaderProof, + CompleteRequest::TransactionIndex(_) => Kind::TransactionIndex, CompleteRequest::Receipts(_) => Kind::Receipts, CompleteRequest::Body(_) => Kind::Body, CompleteRequest::Account(_) => Kind::Account, @@ -301,6 +309,7 @@ impl Request { match *self { Request::Headers(_) => Kind::Headers, Request::HeaderProof(_) => Kind::HeaderProof, + Request::TransactionIndex(_) => Kind::TransactionIndex, Request::Receipts(_) => Kind::Receipts, Request::Body(_) => Kind::Body, Request::Account(_) => Kind::Account, @@ -316,6 +325,7 @@ impl Decodable for Request { match rlp.val_at::(0)? { Kind::Headers => Ok(Request::Headers(rlp.val_at(1)?)), Kind::HeaderProof => Ok(Request::HeaderProof(rlp.val_at(1)?)), + Kind::TransactionIndex => Ok(Request::TransactionIndex(rlp.val_at(1)?)), Kind::Receipts => Ok(Request::Receipts(rlp.val_at(1)?)), Kind::Body => Ok(Request::Body(rlp.val_at(1)?)), Kind::Account => Ok(Request::Account(rlp.val_at(1)?)), @@ -336,6 +346,7 @@ impl Encodable for Request { match *self { Request::Headers(ref req) => s.append(req), Request::HeaderProof(ref req) => s.append(req), + Request::TransactionIndex(ref req) => s.append(req), Request::Receipts(ref req) => s.append(req), Request::Body(ref req) => s.append(req), Request::Account(ref req) => s.append(req), @@ -356,6 +367,7 @@ impl IncompleteRequest for Request { match *self { Request::Headers(ref req) => req.check_outputs(f), Request::HeaderProof(ref req) => req.check_outputs(f), + Request::TransactionIndex(ref req) => req.check_outputs(f), Request::Receipts(ref req) => req.check_outputs(f), Request::Body(ref req) => req.check_outputs(f), Request::Account(ref req) => req.check_outputs(f), @@ -369,6 +381,7 @@ impl IncompleteRequest for Request { match *self { Request::Headers(ref req) => req.note_outputs(f), Request::HeaderProof(ref req) => req.note_outputs(f), + Request::TransactionIndex(ref req) => req.note_outputs(f), Request::Receipts(ref req) => req.note_outputs(f), Request::Body(ref req) => req.note_outputs(f), Request::Account(ref req) => req.note_outputs(f), @@ -382,6 +395,7 @@ impl IncompleteRequest for Request { match *self { Request::Headers(ref mut req) => req.fill(oracle), Request::HeaderProof(ref mut req) => req.fill(oracle), + Request::TransactionIndex(ref mut req) => req.fill(oracle), Request::Receipts(ref mut req) => req.fill(oracle), Request::Body(ref mut req) => req.fill(oracle), Request::Account(ref mut req) => req.fill(oracle), @@ -395,6 +409,7 @@ impl IncompleteRequest for Request { match self { Request::Headers(req) => req.complete().map(CompleteRequest::Headers), Request::HeaderProof(req) => req.complete().map(CompleteRequest::HeaderProof), + Request::TransactionIndex(req) => req.complete().map(CompleteRequest::TransactionIndex), Request::Receipts(req) => req.complete().map(CompleteRequest::Receipts), Request::Body(req) => req.complete().map(CompleteRequest::Body), Request::Account(req) => req.complete().map(CompleteRequest::Account), @@ -408,6 +423,7 @@ impl IncompleteRequest for Request { match *self { Request::Headers(ref mut req) => req.adjust_refs(mapping), Request::HeaderProof(ref mut req) => req.adjust_refs(mapping), + Request::TransactionIndex(ref mut req) => req.adjust_refs(mapping), Request::Receipts(ref mut req) => req.adjust_refs(mapping), Request::Body(ref mut req) => req.adjust_refs(mapping), Request::Account(ref mut req) => req.adjust_refs(mapping), @@ -441,7 +457,8 @@ pub enum Kind { Headers = 0, /// A request for a header proof. HeaderProof = 1, - // TransactionIndex = 2, + /// A request for a transaction index. + TransactionIndex = 2, /// A request for block receipts. Receipts = 3, /// A request for a block body. @@ -461,7 +478,7 @@ impl Decodable for Kind { match rlp.as_val::()? { 0 => Ok(Kind::Headers), 1 => Ok(Kind::HeaderProof), - // 2 => Ok(Kind::TransactionIndex), + 2 => Ok(Kind::TransactionIndex), 3 => Ok(Kind::Receipts), 4 => Ok(Kind::Body), 5 => Ok(Kind::Account), @@ -486,7 +503,8 @@ pub enum Response { Headers(HeadersResponse), /// A response for a header proof (from a CHT) HeaderProof(HeaderProofResponse), - // TransactionIndex, + /// A response for a transaction index. + TransactionIndex(TransactionIndexResponse), /// A response for a block's receipts. Receipts(ReceiptsResponse), /// A response for a block body. @@ -507,6 +525,7 @@ impl ResponseLike for Response { match *self { Response::Headers(ref res) => res.fill_outputs(f), Response::HeaderProof(ref res) => res.fill_outputs(f), + Response::TransactionIndex(ref res) => res.fill_outputs(f), Response::Receipts(ref res) => res.fill_outputs(f), Response::Body(ref res) => res.fill_outputs(f), Response::Account(ref res) => res.fill_outputs(f), @@ -523,6 +542,7 @@ impl Response { match *self { Response::Headers(_) => Kind::Headers, Response::HeaderProof(_) => Kind::HeaderProof, + Response::TransactionIndex(_) => Kind::TransactionIndex, Response::Receipts(_) => Kind::Receipts, Response::Body(_) => Kind::Body, Response::Account(_) => Kind::Account, @@ -538,6 +558,7 @@ impl Decodable for Response { match rlp.val_at::(0)? { Kind::Headers => Ok(Response::Headers(rlp.val_at(1)?)), Kind::HeaderProof => Ok(Response::HeaderProof(rlp.val_at(1)?)), + Kind::TransactionIndex => Ok(Response::TransactionIndex(rlp.val_at(1)?)), Kind::Receipts => Ok(Response::Receipts(rlp.val_at(1)?)), Kind::Body => Ok(Response::Body(rlp.val_at(1)?)), Kind::Account => Ok(Response::Account(rlp.val_at(1)?)), @@ -558,6 +579,7 @@ impl Encodable for Response { match *self { Response::Headers(ref res) => s.append(res), Response::HeaderProof(ref res) => s.append(res), + Response::TransactionIndex(ref res) => s.append(res), Response::Receipts(ref res) => s.append(res), Response::Body(ref res) => s.append(res), Response::Account(ref res) => s.append(res), @@ -864,6 +886,117 @@ pub mod header_proof { } } +/// Request and response for transaction index. +pub mod transaction_index { + use super::{Field, NoSuchOutput, OutputKind, Output}; + use rlp::{Encodable, Decodable, DecoderError, RlpStream, UntrustedRlp}; + use util::H256; + + /// Potentially incomplete transaction index request. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Incomplete { + /// Transaction hash to get index for. + pub hash: Field, + } + + impl Decodable for Incomplete { + fn decode(rlp: &UntrustedRlp) -> Result { + Ok(Incomplete { + hash: rlp.val_at(0)?, + }) + } + } + + impl Encodable for Incomplete { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(1).append(&self.hash); + } + } + + impl super::IncompleteRequest for Incomplete { + type Complete = Complete; + type Response = Response; + + fn check_outputs(&self, mut f: F) -> Result<(), NoSuchOutput> + where F: FnMut(usize, usize, OutputKind) -> Result<(), NoSuchOutput> + { + match self.hash { + Field::Scalar(_) => Ok(()), + Field::BackReference(req, idx) => f(req, idx, OutputKind::Hash), + } + } + + fn note_outputs(&self, mut f: F) where F: FnMut(usize, OutputKind) { + f(0, OutputKind::Number); + f(1, OutputKind::Hash); + } + + fn fill(&mut self, oracle: F) where F: Fn(usize, usize) -> Result { + if let Field::BackReference(req, idx) = self.hash { + self.hash = match oracle(req, idx) { + Ok(Output::Number(hash)) => Field::Scalar(hash.into()), + _ => Field::BackReference(req, idx), + } + } + } + + fn complete(self) -> Result { + Ok(Complete { + hash: self.hash.into_scalar()?, + }) + } + + fn adjust_refs(&mut self, mapping: F) where F: FnMut(usize) -> usize { + self.hash.adjust_req(mapping) + } + } + + /// A complete transaction index request. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Complete { + /// The transaction hash to get index for. + pub hash: H256, + } + + /// The output of a request for transaction index. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Response { + /// Block number. + pub num: u64, + /// Block hash + pub hash: H256, + /// Index in block. + pub index: u64, + } + + impl super::ResponseLike for Response { + /// Fill reusable outputs by providing them to the function. + fn fill_outputs(&self, mut f: F) where F: FnMut(usize, Output) { + f(0, Output::Number(self.num)); + f(1, Output::Hash(self.hash)); + } + } + + impl Decodable for Response { + fn decode(rlp: &UntrustedRlp) -> Result { + Ok(Response { + num: rlp.val_at(0)?, + hash: rlp.val_at(1)?, + index: rlp.val_at(2)?, + }) + } + } + + impl Encodable for Response { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3) + .append(&self.num) + .append(&self.hash) + .append(&self.index); + } + } +} + /// Request and response for block receipts pub mod block_receipts { use super::{Field, NoSuchOutput, OutputKind, Output}; @@ -1704,6 +1837,26 @@ mod tests { check_roundtrip(full_res); } + #[test] + fn transaction_index_roundtrip() { + let req = IncompleteTransactionIndexRequest { + hash: Field::Scalar(Default::default()), + }; + + let full_req = Request::TransactionIndex(req.clone()); + let res = TransactionIndexResponse { + num: 1000, + hash: ::util::H256::random(), + index: 4, + }; + let full_res = Response::TransactionIndex(res.clone()); + + check_roundtrip(req); + check_roundtrip(full_req); + check_roundtrip(res); + check_roundtrip(full_res); + } + #[test] fn receipts_roundtrip() { let req = IncompleteReceiptsRequest {