Skip to content

Commit

Permalink
Light Client: fetch transactions/receipts by transaction hash (openet…
Browse files Browse the repository at this point in the history
…hereum#6641)

* rpc: transaction/receipt requests made async

* rpc: light client fetches transaction and uncle by hash/index

* on_demand: request type for transaction index

* serve transaction index requests in light protocol

* add a test for transaction index serving

* fetch transaction and receipts by hash on light client

* fix decoding tests

* light: more lenient cost table parsing (backwards compatible)

* fix tests and warnings

* LES -> PIP

* Update provider.rs

* proper doc comments for public functions
  • Loading branch information
rphmeier authored and gavofyork committed Oct 8, 2017
1 parent 360ecd3 commit b010fb5
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 114 deletions.
10 changes: 7 additions & 3 deletions ethcore/light/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}",
Expand Down Expand Up @@ -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)?;
}
Expand All @@ -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),
Expand Down
136 changes: 86 additions & 50 deletions ethcore/light/src/net/request_credits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,51 +79,75 @@ 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<U256>, // cost per header
transaction_index: Option<U256>,
body: Option<U256>,
receipts: Option<U256>,
account: Option<U256>,
storage: Option<U256>,
code: Option<U256>,
header_proof: Option<U256>,
transaction_proof: Option<U256>, // cost per gas.
epoch_signal: Option<U256>,
}

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 {
fn default() -> Self {
// 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<U256>, 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);
Expand Down Expand Up @@ -168,21 +192,25 @@ impl Decodable for CostTable {
}
}

let unwrap_cost = |cost: Option<U256>| 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)
}
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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<U256> = 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(),
Expand Down Expand Up @@ -290,25 +318,33 @@ 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<U256> {
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,
Request::Receipts(_) => self.costs.receipts,
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<U256> {
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.
Expand Down Expand Up @@ -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());
}
}
69 changes: 60 additions & 9 deletions ethcore/light/src/net/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ impl Provider for TestProvider {
self.0.client.block_header(id)
}

fn transaction_index(&self, req: request::CompleteTransactionIndexRequest)
-> Option<request::TransactionIndexResponse>
{
Some(request::TransactionIndexResponse {
num: 100,
hash: req.hash,
index: 55,
})
}

fn block_body(&self, req: request::CompleteBodyRequest) -> Option<request::BodyResponse> {
self.0.client.block_body(req)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions ethcore/light/src/on_demand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit b010fb5

Please sign in to comment.