// 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()); } }