// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum 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. // OpenEthereum 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 OpenEthereum. If not, see . use ethcore::snapshot::{ManifestData, SnapshotService}; use ethereum_types::H256; use hash::keccak; use std::{collections::HashSet, iter::FromIterator}; #[derive(PartialEq, Eq, Debug)] pub enum ChunkType { State(H256), Block(H256), } pub struct Snapshot { pending_state_chunks: Vec, pending_block_chunks: Vec, downloading_chunks: HashSet, completed_chunks: HashSet, snapshot_hash: Option, bad_hashes: HashSet, initialized: bool, } impl Snapshot { /// Create a new instance. pub fn new() -> Snapshot { Snapshot { pending_state_chunks: Vec::new(), pending_block_chunks: Vec::new(), downloading_chunks: HashSet::new(), completed_chunks: HashSet::new(), snapshot_hash: None, bad_hashes: HashSet::new(), initialized: false, } } /// Sync the Snapshot completed chunks with the Snapshot Service pub fn initialize(&mut self, snapshot_service: &dyn SnapshotService) { if self.initialized { return; } if let Some(completed_chunks) = snapshot_service.completed_chunks() { self.completed_chunks = HashSet::from_iter(completed_chunks); } trace!( target: "snapshot", "Snapshot is now initialized with {} completed chunks.", self.completed_chunks.len(), ); self.initialized = true; } /// Clear everything. pub fn clear(&mut self) { self.pending_state_chunks.clear(); self.pending_block_chunks.clear(); self.downloading_chunks.clear(); self.completed_chunks.clear(); self.snapshot_hash = None; self.initialized = false; } /// Check if currently downloading a snapshot. pub fn have_manifest(&self) -> bool { self.snapshot_hash.is_some() } /// Reset collection for a manifest RLP pub fn reset_to(&mut self, manifest: &ManifestData, hash: &H256) { self.clear(); self.pending_state_chunks = manifest.state_hashes.clone(); self.pending_block_chunks = manifest.block_hashes.clone(); self.snapshot_hash = Some(hash.clone()); } /// Validate chunk and mark it as downloaded pub fn validate_chunk(&mut self, chunk: &[u8]) -> Result { let hash = keccak(chunk); if self.completed_chunks.contains(&hash) { trace!(target: "sync", "Ignored proccessed chunk: {:x}", hash); return Err(()); } self.downloading_chunks.remove(&hash); if self.pending_block_chunks.iter().any(|h| h == &hash) { self.completed_chunks.insert(hash.clone()); return Ok(ChunkType::Block(hash)); } if self.pending_state_chunks.iter().any(|h| h == &hash) { self.completed_chunks.insert(hash.clone()); return Ok(ChunkType::State(hash)); } trace!(target: "sync", "Ignored unknown chunk: {:x}", hash); Err(()) } /// Find a chunk to download pub fn needed_chunk(&mut self) -> Option { // Find next needed chunk: first block, then state chunks let chunk = { let chunk_filter = |h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h); let needed_block_chunk = self .pending_block_chunks .iter() .filter(|&h| chunk_filter(h)) .map(|h| *h) .next(); // If no block chunks to download, get the state chunks if needed_block_chunk.is_none() { self.pending_state_chunks .iter() .filter(|&h| chunk_filter(h)) .map(|h| *h) .next() } else { needed_block_chunk } }; if let Some(hash) = chunk { self.downloading_chunks.insert(hash.clone()); } chunk } pub fn clear_chunk_download(&mut self, hash: &H256) { self.downloading_chunks.remove(hash); } // note snapshot hash as bad. pub fn note_bad(&mut self, hash: H256) { self.bad_hashes.insert(hash); } // whether snapshot hash is known to be bad. pub fn is_known_bad(&self, hash: &H256) -> bool { self.bad_hashes.contains(hash) } pub fn snapshot_hash(&self) -> Option { self.snapshot_hash } pub fn total_chunks(&self) -> usize { self.pending_block_chunks.len() + self.pending_state_chunks.len() } pub fn done_chunks(&self) -> usize { self.completed_chunks.len() } pub fn is_complete(&self) -> bool { self.total_chunks() == self.completed_chunks.len() } } #[cfg(test)] mod test { use super::*; use bytes::Bytes; use ethcore::snapshot::ManifestData; use hash::keccak; fn is_empty(snapshot: &Snapshot) -> bool { snapshot.pending_block_chunks.is_empty() && snapshot.pending_state_chunks.is_empty() && snapshot.completed_chunks.is_empty() && snapshot.downloading_chunks.is_empty() && snapshot.snapshot_hash.is_none() } fn test_manifest() -> (ManifestData, H256, Vec, Vec) { let state_chunks: Vec = (0..20) .map(|_| H256::random().as_bytes().to_vec()) .collect(); let block_chunks: Vec = (0..20) .map(|_| H256::random().as_bytes().to_vec()) .collect(); let manifest = ManifestData { version: 2, state_hashes: state_chunks.iter().map(|data| keccak(data)).collect(), block_hashes: block_chunks.iter().map(|data| keccak(data)).collect(), state_root: H256::default(), block_number: 42, block_hash: H256::default(), }; let mhash = keccak(manifest.clone().into_rlp()); (manifest, mhash, state_chunks, block_chunks) } #[test] fn create_clear() { let mut snapshot = Snapshot::new(); assert!(is_empty(&snapshot)); let (manifest, mhash, _, _) = test_manifest(); snapshot.reset_to(&manifest, &mhash); assert!(!is_empty(&snapshot)); snapshot.clear(); assert!(is_empty(&snapshot)); } #[test] fn validate_chunks() { let mut snapshot = Snapshot::new(); let (manifest, mhash, state_chunks, block_chunks) = test_manifest(); snapshot.reset_to(&manifest, &mhash); assert_eq!(snapshot.done_chunks(), 0); assert!(snapshot .validate_chunk(&H256::random().as_bytes().to_vec()) .is_err()); let requested: Vec = (0..40).map(|_| snapshot.needed_chunk().unwrap()).collect(); assert!(snapshot.needed_chunk().is_none()); let requested_all_block_chunks = manifest .block_hashes .iter() .all(|h| requested.iter().any(|rh| rh == h)); assert!(requested_all_block_chunks); let requested_all_state_chunks = manifest .state_hashes .iter() .all(|h| requested.iter().any(|rh| rh == h)); assert!(requested_all_state_chunks); assert_eq!(snapshot.downloading_chunks.len(), 40); assert_eq!( snapshot.validate_chunk(&state_chunks[4]), Ok(ChunkType::State(manifest.state_hashes[4].clone())) ); assert_eq!(snapshot.completed_chunks.len(), 1); assert_eq!(snapshot.downloading_chunks.len(), 39); assert_eq!( snapshot.validate_chunk(&block_chunks[10]), Ok(ChunkType::Block(manifest.block_hashes[10].clone())) ); assert_eq!(snapshot.completed_chunks.len(), 2); assert_eq!(snapshot.downloading_chunks.len(), 38); for (i, data) in state_chunks.iter().enumerate() { if i != 4 { assert!(snapshot.validate_chunk(data).is_ok()); } } for (i, data) in block_chunks.iter().enumerate() { if i != 10 { assert!(snapshot.validate_chunk(data).is_ok()); } } assert!(snapshot.is_complete()); assert_eq!(snapshot.done_chunks(), 40); assert_eq!(snapshot.done_chunks(), snapshot.total_chunks()); assert_eq!(snapshot.snapshot_hash(), Some(keccak(manifest.into_rlp()))); } #[test] fn tracks_known_bad() { let mut snapshot = Snapshot::new(); let hash = H256::random(); assert_eq!(snapshot.is_known_bad(&hash), false); snapshot.note_bad(hash); assert_eq!(snapshot.is_known_bad(&hash), true); } }