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:
committed by
Arkadiy Paronyan
parent
aa41b48ba0
commit
386cdb830d
@@ -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) =>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user