// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see .
//! Cache for data fetched from the network.
//!
//! Stores ancient block headers, bodies, receipts, and total difficulties.
//! Furthermore, stores a "gas price corpus" of relative recency, which is a sorted
//! vector of all gas prices from a recent range of blocks.
use std::time::{Instant, Duration};
use parity_util_mem::{MallocSizeOf, MallocSizeOfOps, MallocSizeOfExt};
use common_types::encoded;
use common_types::BlockNumber;
use common_types::receipt::Receipt;
use ethereum_types::{H256, U256};
use memory_cache::MemoryLruCache;
use stats::Corpus;
/// Configuration for how much data to cache.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CacheSizes {
/// Maximum size, in bytes, of cached headers.
pub headers: usize,
/// Maximum size, in bytes, of cached canonical hashes.
pub canon_hashes: usize,
/// Maximum size, in bytes, of cached block bodies.
pub bodies: usize,
/// Maximum size, in bytes, of cached block receipts.
pub receipts: usize,
/// Maximum size, in bytes, of cached chain score for the block.
pub chain_score: usize,
}
impl Default for CacheSizes {
fn default() -> Self {
const MB: usize = 1024 * 1024;
CacheSizes {
headers: 10 * MB,
canon_hashes: 3 * MB,
bodies: 20 * MB,
receipts: 10 * MB,
chain_score: 7 * MB,
}
}
}
/// The light client data cache.
///
/// Note that almost all getter methods take `&mut self` due to the necessity to update
/// the underlying LRU-caches on read.
/// [LRU-cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_.28LRU.29)
pub struct Cache {
headers: MemoryLruCache,
canon_hashes: MemoryLruCache,
bodies: MemoryLruCache,
receipts: MemoryLruCache>,
chain_score: MemoryLruCache,
corpus: Option<(Corpus, Instant)>,
corpus_expiration: Duration,
}
impl Cache {
/// Create a new data cache with the given sizes and gas price corpus expiration time.
pub fn new(sizes: CacheSizes, corpus_expiration: Duration) -> Self {
Cache {
headers: MemoryLruCache::new(sizes.headers),
canon_hashes: MemoryLruCache::new(sizes.canon_hashes),
bodies: MemoryLruCache::new(sizes.bodies),
receipts: MemoryLruCache::new(sizes.receipts),
chain_score: MemoryLruCache::new(sizes.chain_score),
corpus: None,
corpus_expiration,
}
}
/// Query header by hash.
pub fn block_header(&mut self, hash: &H256) -> Option {
self.headers.get_mut(hash).cloned()
}
/// Query hash by number.
pub fn block_hash(&mut self, num: BlockNumber) -> Option {
self.canon_hashes.get_mut(&num).map(|h| *h)
}
/// Query block body by block hash.
pub fn block_body(&mut self, hash: &H256) -> Option {
self.bodies.get_mut(hash).cloned()
}
/// Query block receipts by block hash.
pub fn block_receipts(&mut self, hash: &H256) -> Option> {
self.receipts.get_mut(hash).cloned()
}
/// Query chain score by block hash.
pub fn chain_score(&mut self, hash: &H256) -> Option {
self.chain_score.get_mut(hash).map(|h| *h)
}
/// Cache the given header.
pub fn insert_block_header(&mut self, hash: H256, hdr: encoded::Header) {
self.headers.insert(hash, hdr);
}
/// Cache the given canonical block hash.
pub fn insert_block_hash(&mut self, num: BlockNumber, hash: H256) {
self.canon_hashes.insert(num, hash);
}
/// Cache the given block body.
pub fn insert_block_body(&mut self, hash: H256, body: encoded::Body) {
self.bodies.insert(hash, body);
}
/// Cache the given block receipts.
pub fn insert_block_receipts(&mut self, hash: H256, receipts: Vec) {
self.receipts.insert(hash, receipts);
}
/// Cache the given chain scoring.
pub fn insert_chain_score(&mut self, hash: H256, score: U256) {
self.chain_score.insert(hash, score);
}
/// Get gas price corpus, if recent enough.
pub fn gas_price_corpus(&self) -> Option> {
let now = Instant::now();
self.corpus.as_ref().and_then(|&(ref corpus, ref tm)| {
if *tm + self.corpus_expiration >= now {
Some(corpus.clone())
} else {
None
}
})
}
/// Set the cached gas price corpus.
pub fn set_gas_price_corpus(&mut self, corpus: Corpus) {
self.corpus = Some((corpus, Instant::now()))
}
/// Get the memory used.
pub fn mem_used(&self) -> usize {
self.malloc_size_of()
}
}
// This is fast method: it is possible to have a more exhaustive implementation
impl MallocSizeOf for Cache {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
self.headers.current_size()
+ self.canon_hashes.current_size()
+ self.bodies.current_size()
+ self.receipts.current_size()
+ self.chain_score.current_size()
// `self.corpus` is skipped
}
}
#[cfg(test)]
mod tests {
use super::Cache;
use std::time::Duration;
#[test]
fn corpus_inaccessible() {
let duration = Duration::from_secs(20);
let mut cache = Cache::new(Default::default(), duration.clone());
cache.set_gas_price_corpus(vec![].into());
assert_eq!(cache.gas_price_corpus(), Some(vec![].into()));
{
let corpus_time = &mut cache.corpus.as_mut().unwrap().1;
*corpus_time = *corpus_time - duration;
}
assert!(cache.gas_price_corpus().is_none());
}
}