Back-references for the on-demand service (#5573)

* header back-references for on demand

* initial back-reference implementation for on demand requests

* answer requests from cache

* answer requests from cache, add tests

* strongly typed responses for vectors of homogeneous requests

* fix fallout in RPC without optimizing
This commit is contained in:
Robert Habermeier
2017-05-23 06:39:25 -04:00
committed by Arkadiy Paronyan
parent aa41b48ba0
commit 386cdb830d
9 changed files with 778 additions and 426 deletions

View File

@@ -20,7 +20,7 @@ use std::fmt::Debug;
use std::ops::Deref;
use std::sync::{Arc, Weak};
use futures::{future, stream, Future, Stream, BoxFuture};
use futures::{future, Future, BoxFuture};
use light::cache::Cache as LightDataCache;
use light::client::LightChainClient;
use light::on_demand::{request, OnDemand};
@@ -185,25 +185,28 @@ pub fn fetch_gas_price_corpus(
let eventual_corpus = sync.with_context(|ctx| {
// get some recent headers with gas used,
// and request each of the blocks from the network.
let block_futures = client.ancestry_iter(BlockId::Latest)
let block_requests = client.ancestry_iter(BlockId::Latest)
.filter(|hdr| hdr.gas_used() != U256::default())
.take(GAS_PRICE_SAMPLE_SIZE)
.map(request::Body::new)
.map(|req| on_demand.block(ctx, req));
.map(|hdr| request::Body(hdr.into()))
.collect::<Vec<_>>();
// as the blocks come in, collect gas prices into a vector
stream::futures_unordered(block_futures)
.fold(Vec::new(), |mut v, block| {
for t in block.transaction_views().iter() {
v.push(t.gas_price())
}
// when the blocks come in, collect gas prices into a vector
on_demand.request(ctx, block_requests)
.expect("no back-references; therefore all back-references are valid; qed")
.map(|bodies| {
bodies.into_iter().fold(Vec::new(), |mut v, block| {
for t in block.transaction_views().iter() {
v.push(t.gas_price())
}
future::ok(v)
v
})
})
.map(move |v| {
.map(move |prices| {
// produce a corpus from the vector, cache it, and return
// the median as the intended gas price.
let corpus: ::stats::Corpus<_> = v.into();
let corpus: ::stats::Corpus<_> = prices.into();
cache.lock().set_gas_price_corpus(corpus.clone());
corpus
})
@@ -282,10 +285,10 @@ impl LightDispatcher {
let best_header = self.client.best_block_header();
let account_start_nonce = self.client.engine().account_start_nonce();
let nonce_future = self.sync.with_context(|ctx| self.on_demand.account(ctx, request::Account {
header: best_header,
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
header: best_header.into(),
address: addr,
}));
}).expect("no back-references; therefore all back-references valid; qed"));
match nonce_future {
Some(x) =>

View File

@@ -31,7 +31,8 @@ use jsonrpc_macros::Trailing;
use light::cache::Cache;
use light::client::LightChainClient;
use light::cht;
use light::on_demand::{OnDemand, request};
use light::on_demand::{request, OnDemand, HeaderRef, Request as OnDemandRequest, Response as OnDemandResponse};
use light::request::Field;
use ethsync::LightSync;
use util::{Address, Mutex, Uint, U256};
@@ -55,51 +56,72 @@ pub struct LightFetch {
/// Type alias for convenience.
pub type ExecutionResult = Result<Executed, ExecutionError>;
// extract the header indicated by the given `HeaderRef` from the given responses.
// fails only if they do not correspond.
fn extract_header(res: &[OnDemandResponse], header: HeaderRef) -> Option<encoded::Header> {
match header {
HeaderRef::Stored(hdr) => Some(hdr),
HeaderRef::Unresolved(idx, _) => match res.get(idx) {
Some(&OnDemandResponse::HeaderByHash(ref hdr)) => Some(hdr.clone()),
_ => None,
},
}
}
impl LightFetch {
/// Get a block header from the on demand service or client, or error.
pub fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> {
// push the necessary requests onto the request chain to get the header by the given ID.
// yield a header reference which other requests can use.
fn make_header_requests(&self, id: BlockId, reqs: &mut Vec<OnDemandRequest>) -> Result<HeaderRef, Error> {
if let Some(h) = self.client.block_header(id) {
return future::ok(h).boxed()
return Ok(h.into());
}
let maybe_future = match id {
match id {
BlockId::Number(n) => {
let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize));
match cht_root {
None => return future::err(errors::unknown_block()).boxed(),
None => Err(errors::unknown_block()),
Some(root) => {
let req = request::HeaderProof::new(n, root)
.expect("only fails for 0; client always stores genesis; client already queried; qed");
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.sync.with_context(|ctx| {
let fut = self.on_demand.hash_by_number(ctx, req)
.map(request::HeaderByHash)
.map_err(errors::on_demand_cancel);
let idx = reqs.len();
let hash_ref = Field::back_ref(idx, 0);
reqs.push(req.into());
reqs.push(request::HeaderByHash(hash_ref.clone()).into());
fut.and_then(move |req| {
match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
})
Ok(HeaderRef::Unresolved(idx + 1, hash_ref))
}
}
}
BlockId::Hash(h) => {
self.sync.with_context(|ctx|
self.on_demand.header_by_hash(ctx, request::HeaderByHash(h))
.then(|res| future::done(match res {
Ok(h) => Ok(h),
Err(e) => Err(errors::on_demand_cancel(e)),
}))
.boxed()
)
reqs.push(request::HeaderByHash(h.into()).into());
let idx = reqs.len();
Ok(HeaderRef::Unresolved(idx, h.into()))
}
_ => None, // latest, earliest, and pending will have all already returned.
_ => Err(errors::unknown_block()) // latest, earliest, and pending will have all already returned.
}
}
/// Get a block header from the on demand service or client, or error.
pub fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> {
let mut reqs = Vec::new();
let header_ref = match self.make_header_requests(id, &mut reqs) {
Ok(r) => r,
Err(e) => return future::err(e).boxed(),
};
let maybe_future = self.sync.with_context(move |ctx| {
self.on_demand.request_raw(ctx, reqs)
.expect("all back-references known to be valid; qed")
.map(|res| extract_header(&res, header_ref)
.expect("these responses correspond to requests that header_ref belongs to. \
therefore it will not fail; qed"))
.map_err(errors::on_demand_cancel)
.boxed()
});
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
@@ -109,19 +131,29 @@ impl LightFetch {
/// 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<Option<BasicAccount>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
let mut reqs = Vec::new();
let header_ref = match self.make_header_requests(id, &mut reqs) {
Ok(r) => r,
Err(e) => return future::err(e).boxed(),
};
self.header(id).and_then(move |header| {
let maybe_fut = sync.with_context(|ctx| on_demand.account(ctx, request::Account {
header: header,
address: address,
}));
reqs.push(request::Account { header: header_ref, address: address }.into());
match maybe_fut {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
let maybe_future = self.sync.with_context(move |ctx| {
self.on_demand.request_raw(ctx, reqs)
.expect("all back-references known to be valid; qed")
.map(|mut res| match res.pop() {
Some(OnDemandResponse::Account(acc)) => acc,
_ => panic!("responses correspond directly with requests in amount and type; qed"),
})
.map_err(errors::on_demand_cancel)
.boxed()
});
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
}
}
/// helper for getting proved execution.
@@ -182,13 +214,16 @@ impl LightFetch {
let request = request::TransactionProof {
tx: tx,
header: hdr,
header: hdr.into(),
env_info: env_info,
engine: client.engine().clone(),
};
let proved_future = sync.with_context(move |ctx| {
on_demand.transaction_proof(ctx, request).map_err(errors::on_demand_cancel).boxed()
on_demand
.request(ctx, request)
.expect("no back-references; therefore all back-refs valid; qed")
.map_err(errors::on_demand_cancel).boxed()
});
match proved_future {
@@ -200,13 +235,28 @@ impl LightFetch {
/// get a block itself. fails on unknown block ID.
pub fn block(&self, id: BlockId) -> BoxFuture<encoded::Block, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
let mut reqs = Vec::new();
let header_ref = match self.make_header_requests(id, &mut reqs) {
Ok(r) => r,
Err(e) => return future::err(e).boxed(),
};
self.header(id).map(request::Body::new).and_then(move |req| {
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
reqs.push(request::Body(header_ref).into());
let maybe_future = self.sync.with_context(move |ctx| {
self.on_demand.request_raw(ctx, reqs)
.expect("all back-references known to be valid; qed")
.map(|mut res| match res.pop() {
Some(OnDemandResponse::Body(b)) => b,
_ => panic!("responses correspond directly with requests in amount and type; qed"),
})
.map_err(errors::on_demand_cancel)
.boxed()
});
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
}
}
}

View File

@@ -59,6 +59,8 @@ use v1::metadata::Metadata;
use util::Address;
const NO_INVALID_BACK_REFS: &'static str = "Fails only on invalid back-references; back-references here known to be valid; qed";
/// Light client `ETH` (and filter) RPC.
pub struct EthClient {
sync: Arc<LightSync>,
@@ -186,16 +188,17 @@ impl EthClient {
// - network is down.
// - we get a score, but our hash is non-canonical.
// - we get a score, and our hash is canonical.
let maybe_fut = sync.with_context(move |ctx| on_demand.hash_and_score_by_number(ctx, req));
let maybe_fut = sync.with_context(move |ctx| on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS));
match maybe_fut {
Some(fut) => fut.map(move |(hash, score)| {
Some(fut) => fut
.map(move |(hash, score)| {
let score = if hash == block.hash() {
Some(score)
} else {
None
};
fill_rich(block, score)
fill_rich(block, score)
}).map_err(errors::on_demand_cancel).boxed(),
None => return future::err(errors::network_disabled()).boxed(),
}
@@ -295,7 +298,8 @@ impl Eth for EthClient {
if hdr.transactions_root() == SHA3_NULL_RLP {
future::ok(Some(U256::from(0).into())).boxed()
} else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
sync.with_context(|ctx| on_demand.request(ctx, request::Body(hdr.into())))
.map(|x| x.expect(NO_INVALID_BACK_REFS))
.map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into())))
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
@@ -310,7 +314,8 @@ impl Eth for EthClient {
if hdr.transactions_root() == SHA3_NULL_RLP {
future::ok(Some(U256::from(0).into())).boxed()
} else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
sync.with_context(|ctx| on_demand.request(ctx, request::Body(hdr.into())))
.map(|x| x.expect(NO_INVALID_BACK_REFS))
.map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into())))
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
@@ -325,7 +330,8 @@ impl Eth for EthClient {
if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP {
future::ok(Some(U256::from(0).into())).boxed()
} else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
sync.with_context(|ctx| on_demand.request(ctx, request::Body(hdr.into())))
.map(|x| x.expect(NO_INVALID_BACK_REFS))
.map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into())))
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
@@ -340,7 +346,8 @@ impl Eth for EthClient {
if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP {
future::ok(Some(U256::from(0).into())).boxed()
} else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
sync.with_context(|ctx| on_demand.request(ctx, request::Body(hdr.into())))
.map(|x| x.expect(NO_INVALID_BACK_REFS))
.map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into())))
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
@@ -501,8 +508,8 @@ impl Filterable for EthClient {
let hdr_bloom = hdr.log_bloom();
bit_combos.iter().find(|&bloom| hdr_bloom & *bloom == *bloom).is_some()
})
.map(|hdr| (hdr.number(), request::BlockReceipts(hdr)))
.map(|(num, req)| self.on_demand.block_receipts(ctx, req).map(move |x| (num, x)))
.map(|hdr| (hdr.number(), request::BlockReceipts(hdr.into())))
.map(|(num, req)| self.on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS).map(move |x| (num, x)))
.collect();
// as the receipts come in, find logs within them which match the filter.