diff --git a/ethcore/src/blockchain.rs b/ethcore/src/blockchain.rs index 1a329610c..052e64d4c 100644 --- a/ethcore/src/blockchain.rs +++ b/ethcore/src/blockchain.rs @@ -50,7 +50,9 @@ pub struct CacheSize { /// Logs cache size. pub block_logs: usize, /// Blooms cache size. - pub blocks_blooms: usize + pub blocks_blooms: usize, + /// Block receipts size. + pub block_receipts: usize } struct BloomIndexer { @@ -147,6 +149,9 @@ pub trait BlockProvider { /// Get the address of transaction with given hash. fn transaction_address(&self, hash: &H256) -> Option; + /// Get receipts of block with given hash. + fn block_receipts(&self, hash: &H256) -> Option; + /// Get the partial-header of a block. fn block_header(&self, hash: &H256) -> Option
{ self.block(hash).map(|bytes| BlockView::new(&bytes).header()) @@ -223,6 +228,7 @@ pub struct BlockChain { transaction_addresses: RwLock>, block_logs: RwLock>, blocks_blooms: RwLock>, + block_receipts: RwLock>, extras_db: DB, blocks_db: DB, @@ -287,6 +293,11 @@ impl BlockProvider for BlockChain { self.query_extras(hash, &self.transaction_addresses) } + /// Get receipts of block with given hash. + fn block_receipts(&self, hash: &H256) -> Option { + self.query_extras(hash, &self.block_receipts) + } + /// Returns numbers of blocks containing given bloom. fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockNumber, to_block: BlockNumber) -> Vec { let filter = ChainFilter::new(self, self.bloom_indexer.index_size(), self.bloom_indexer.levels()); @@ -348,6 +359,7 @@ impl BlockChain { transaction_addresses: RwLock::new(HashMap::new()), block_logs: RwLock::new(HashMap::new()), blocks_blooms: RwLock::new(HashMap::new()), + block_receipts: RwLock::new(HashMap::new()), extras_db: extras_db, blocks_db: blocks_db, cache_man: RwLock::new(cache_man), @@ -504,7 +516,7 @@ impl BlockChain { /// Inserts the block into backing cache database. /// Expects the block to be valid and already verified. /// If the block is already known, does nothing. - pub fn insert_block(&self, bytes: &[u8], receipts: &[Receipt]) { + pub fn insert_block(&self, bytes: &[u8], receipts: Vec) { // create views onto rlp let block = BlockView::new(bytes); let header = block.header_view(); @@ -546,8 +558,7 @@ impl BlockChain { /// Transforms block into WriteBatch that may be written into database /// Additionally, if it's new best block it returns new best block object. - //fn block_to_extras_insert_batch(&self, bytes: &[u8], _receipts: &[Receipt]) -> (WriteBatch, Option, BlockDetails) { - fn block_to_extras_update(&self, bytes: &[u8], _receipts: &[Receipt]) -> ExtrasUpdate { + fn block_to_extras_update(&self, bytes: &[u8], receipts: Vec) -> ExtrasUpdate { // create views onto rlp let block = BlockView::new(bytes); let header = block.header_view(); @@ -585,11 +596,8 @@ impl BlockChain { }); } - // save blooms (is it really required?). maybe store receipt whole instead? - //let blooms: Vec = receipts.iter().map(|r| r.log_bloom.clone()).collect(); - //batch.put_extras(&hash, &BlockLogBlooms { - //blooms: blooms - //}); + // update block receipts + batch.put_extras(&hash, &BlockReceipts::new(receipts)); // if it's not new best block, just return if !is_new_best { @@ -741,7 +749,8 @@ impl BlockChain { block_details: self.block_details.read().unwrap().heap_size_of_children(), transaction_addresses: self.transaction_addresses.read().unwrap().heap_size_of_children(), block_logs: self.block_logs.read().unwrap().heap_size_of_children(), - blocks_blooms: self.blocks_blooms.read().unwrap().heap_size_of_children() + blocks_blooms: self.blocks_blooms.read().unwrap().heap_size_of_children(), + block_receipts: self.block_receipts.read().unwrap().heap_size_of_children() } } @@ -773,6 +782,7 @@ impl BlockChain { let mut transaction_addresses = self.transaction_addresses.write().unwrap(); let mut block_logs = self.block_logs.write().unwrap(); let mut blocks_blooms = self.blocks_blooms.write().unwrap(); + let mut block_receipts = self.block_receipts.write().unwrap(); for id in cache_man.cache_usage.pop_back().unwrap().into_iter() { cache_man.in_use.remove(&id); @@ -782,6 +792,7 @@ impl BlockChain { CacheID::Extras(ExtrasIndex::TransactionAddress, h) => { transaction_addresses.remove(&h); }, CacheID::Extras(ExtrasIndex::BlockLogBlooms, h) => { block_logs.remove(&h); }, CacheID::Extras(ExtrasIndex::BlocksBlooms, h) => { blocks_blooms.remove(&h); }, + CacheID::Extras(ExtrasIndex::BlockReceipts, h) => { block_receipts.remove(&h); }, _ => panic!(), } } @@ -823,7 +834,7 @@ mod tests { let first = "f90285f90219a03caa2203f3d7c136c0295ed128a7d31cea520b1ca5e27afe17d0853331798942a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0bac6177a79e910c98d86ec31a09ae37ac2de15b754fd7bed1ba52362c49416bfa0d45893a296c1490a978e0bd321b5f2635d8280365c1fe9f693d65f233e791344a0c7778a7376099ee2e5c455791c1885b5c361b95713fddcbe32d97fd01334d296b90100000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000400000000000000000000000000000000000000000000000000000008302000001832fefd882560b845627cb99a00102030405060708091011121314151617181920212223242526272829303132a08ccb2837fb2923bd97e8f2d08ea32012d6e34be018c73e49a0f98843e8f47d5d88e53be49fec01012ef866f864800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d8785012a05f200801ba0cb088b8d2ff76a7b2c6616c9d02fb6b7a501afbf8b69d7180b09928a1b80b5e4a06448fe7476c606582039bb72a9f6f4b4fad18507b8dfbd00eebbe151cc573cd2c0".from_hex().unwrap(); - bc.insert_block(&first, &[]); + bc.insert_block(&first, vec![]); let first_hash = H256::from_str("a940e5af7d146b3b917c953a82e1966b906dace3a4e355b5b0a4560190357ea1").unwrap(); @@ -856,10 +867,10 @@ mod tests { let temp = RandomTempPath::new(); let bc = BlockChain::new(&genesis, temp.as_path()); - bc.insert_block(&b1, &[]); - bc.insert_block(&b2, &[]); - bc.insert_block(&b3a, &[]); - bc.insert_block(&b3b, &[]); + bc.insert_block(&b1, vec![]); + bc.insert_block(&b2, vec![]); + bc.insert_block(&b3a, vec![]); + bc.insert_block(&b3b, vec![]); assert_eq!(bc.best_block_hash(), best_block_hash); assert_eq!(bc.block_number(&genesis_hash).unwrap(), 0); @@ -936,7 +947,7 @@ mod tests { { let bc = BlockChain::new(&genesis, temp.as_path()); assert_eq!(bc.best_block_hash(), genesis_hash); - bc.insert_block(&b1, &[]); + bc.insert_block(&b1, vec![]); assert_eq!(bc.best_block_hash(), b1_hash); } @@ -995,7 +1006,7 @@ mod tests { let temp = RandomTempPath::new(); let bc = BlockChain::new(&genesis, temp.as_path()); - bc.insert_block(&b1, &[]); + bc.insert_block(&b1, vec![]); let transactions = bc.transactions(&b1_hash).unwrap(); assert_eq!(transactions.len(), 7); @@ -1026,13 +1037,13 @@ mod tests { assert_eq!(blocks_b1, vec![]); assert_eq!(blocks_b2, vec![]); - bc.insert_block(&b1, &[]); + bc.insert_block(&b1, vec![]); let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 3); let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 3); assert_eq!(blocks_b1, vec![1]); assert_eq!(blocks_b2, vec![]); - bc.insert_block(&b2, &[]); + bc.insert_block(&b2, vec![]); let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 3); let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 3); assert_eq!(blocks_b1, vec![1]); diff --git a/ethcore/src/client.rs b/ethcore/src/client.rs index d7fcdbc30..7fe3fe836 100644 --- a/ethcore/src/client.rs +++ b/ethcore/src/client.rs @@ -33,7 +33,9 @@ use env_info::LastHashes; use verification::*; use block::*; use transaction::LocalizedTransaction; -use extras::TransactionAddress; +use extras::{TransactionAddress, BlockReceipts}; +use filter::Filter; +use log_entry::LocalizedLogEntry; pub use blockchain::TreeRoute; /// Uniquely identifies block. @@ -147,6 +149,9 @@ pub trait BlockChainClient : Sync + Send { /// Returns numbers of blocks containing given bloom. fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option>; + + /// Returns logs matching given filter. + fn logs(&self, filter: Filter) -> Vec; } #[derive(Default, Clone, Debug, Eq, PartialEq)] @@ -307,7 +312,7 @@ impl Client { good_blocks.push(header.hash().clone()); - self.chain.write().unwrap().insert_block(&block.bytes, result.block().receipts()); //TODO: err here? + self.chain.write().unwrap().insert_block(&block.bytes, result.block().receipts().clone()); //TODO: err here? let ancient = if header.number() >= HISTORY { Some(header.number() - HISTORY) } else { None }; match result.drain().commit(header.number(), &header.hash(), ancient.map(|n|(n, self.chain.read().unwrap().block_hash(n).unwrap()))) { Ok(_) => (), @@ -474,6 +479,50 @@ impl BlockChainClient for Client { _ => None } } + + fn logs(&self, filter: Filter) -> Vec { + let mut blocks = filter.bloom_possibilities().iter() + .map(|bloom| self.blocks_with_bloom(bloom, filter.from_block.clone(), filter.to_block.clone())) + .filter_map(|m| m) + .flat_map(|m| m) + // remove duplicate elements + .collect::>() + .into_iter() + .collect::>(); + + blocks.sort(); + + blocks.into_iter() + .map(|number| self.chain.read().unwrap().block_hash(number).map(|hash| (number, hash))) + .filter_map(|m| m) + .map(|(number, hash)| self.chain.read().unwrap().block_receipts(&hash).map(|r| (number, hash, r.receipts))) + .filter_map(|m| m) + .map(|(number, hash, receipts)| { + let mut log_index = 0; + receipts.into_iter() + .enumerate() + .map(|(index, receipt)| { + log_index += receipt.logs.len(); + receipt.logs.into_iter() + .enumerate() + .filter(|tuple| filter.matches(&tuple.1)) + .map(|(i, log)| LocalizedLogEntry { + entry: log, + block_hash: hash.clone(), + block_number: number as usize, + transaction_hash: H256::new(), + transaction_index: index, + log_index: log_index + }) + .collect::>() + }) + .flat_map(|m| m) + .collect::>() + + }) + .flat_map(|m| m) + .collect() + } } impl MayPanic for Client { diff --git a/ethcore/src/extras.rs b/ethcore/src/extras.rs index 66e734582..50d3a21bf 100644 --- a/ethcore/src/extras.rs +++ b/ethcore/src/extras.rs @@ -16,9 +16,10 @@ //! Blockchain DB extras. +use rocksdb::{DB, Writable}; use util::*; use header::BlockNumber; -use rocksdb::{DB, Writable}; +use receipt::Receipt; /// Represents index of extra data in database #[derive(Copy, Debug, Hash, Eq, PartialEq, Clone)] @@ -32,7 +33,9 @@ pub enum ExtrasIndex { /// Block log blooms index BlockLogBlooms = 3, /// Block blooms index - BlocksBlooms = 4 + BlocksBlooms = 4, + /// Block receipts index + BlockReceipts } /// trait used to write Extras data to db @@ -307,3 +310,43 @@ impl Encodable for TransactionAddress { s.append(&self.index); } } + +/// Contains all block receipts. +#[derive(Clone)] +pub struct BlockReceipts { + pub receipts: Vec +} + +impl BlockReceipts { + pub fn new(receipts: Vec) -> Self { + BlockReceipts { + receipts: receipts + } + } +} + +impl Decodable for BlockReceipts { + fn decode(decoder: &D) -> Result where D: Decoder { + Ok(BlockReceipts { + receipts: try!(Decodable::decode(decoder)) + }) + } +} + +impl Encodable for BlockReceipts { + fn rlp_append(&self, s: &mut RlpStream) { + s.append(&self.receipts); + } +} + +impl HeapSizeOf for BlockReceipts { + fn heap_size_of_children(&self) -> usize { + self.receipts.heap_size_of_children() + } +} + +impl ExtrasIndexable for BlockReceipts { + fn extras_index() -> ExtrasIndex { + ExtrasIndex::BlockReceipts + } +} diff --git a/ethcore/src/filter.rs b/ethcore/src/filter.rs index 718656191..89295b9c2 100644 --- a/ethcore/src/filter.rs +++ b/ethcore/src/filter.rs @@ -17,26 +17,27 @@ use util::hash::*; use util::sha3::*; use client::BlockId; +use log_entry::LogEntry; /// Blockchain log filter data. pub struct Filter { /// Blockchain will be searched from this block. - from_block: BlockId, + pub from_block: BlockId, /// Till this block. - to_block: BlockId, + pub to_block: BlockId, /// Search addresses. /// /// If None, match all. /// If specified, log must be produced by one of these addresses. - address: Option>, + pub address: Option>, /// Search topics. /// /// If None, match all. /// If specified, log must contain one of these topics. - topics: [Option>; 4] + pub topics: [Option>; 4] } impl Filter { @@ -63,6 +64,23 @@ impl Filter { }).flat_map(|m| m).collect() }) } + + /// Returns true if given log entry matches filter. + pub fn matches(&self, log: &LogEntry) -> bool { + let matches = match self.address { + Some(ref addresses) if !addresses.is_empty() => addresses.iter().fold(false, |res, address| { + res || &log.address == address + }), + _ => true + }; + + matches && self.topics.iter().enumerate().fold(true, |res, (i, topic)| match *topic { + Some(ref topics) if !topics.is_empty() => res && topics.iter().fold(false, | acc, topic | { + acc || log.topics.get(i) == Some(topic) + }), + _ => res, + }) + } } #[cfg(test)] @@ -71,6 +89,7 @@ mod tests { use util::hash::*; use filter::Filter; use client::BlockId; + use log_entry::LogEntry; #[test] fn test_bloom_possibilities_none() { @@ -149,4 +168,49 @@ mod tests { assert_eq!(possibilities[0], H2048::from_str("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000004000000004000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()); } + #[test] + fn test_filter_matches() { + let filter = Filter { + from_block: BlockId::Earliest, + to_block: BlockId::Latest, + address: Some(vec![Address::from_str("b372018f3be9e171df0581136b59d2faf73a7d5d").unwrap()]), + topics: [ + Some(vec![H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap()]), + Some(vec![H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23fa").unwrap()]), + None, None + ] + }; + + let entry0 = LogEntry { + address: Address::from_str("b372018f3be9e171df0581136b59d2faf73a7d5d").unwrap(), + topics: vec![ + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap(), + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23fa").unwrap(), + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap(), + ], + data: vec![] + }; + + let entry1 = LogEntry { + address: Address::from_str("b372018f3be9e171df0581136b59d2faf73a7d5e").unwrap(), + topics: vec![ + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap(), + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23fa").unwrap(), + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap(), + ], + data: vec![] + }; + + let entry2 = LogEntry { + address: Address::from_str("b372018f3be9e171df0581136b59d2faf73a7d5d").unwrap(), + topics: vec![ + H256::from_str("ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9").unwrap(), + ], + data: vec![] + }; + + assert_eq!(filter.matches(&entry0), true); + assert_eq!(filter.matches(&entry1), false); + assert_eq!(filter.matches(&entry2), false); + } } diff --git a/ethcore/src/log_entry.rs b/ethcore/src/log_entry.rs index 304fae9d0..24f449dce 100644 --- a/ethcore/src/log_entry.rs +++ b/ethcore/src/log_entry.rs @@ -37,6 +37,24 @@ impl Encodable for LogEntry { } } +impl Decodable for LogEntry { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let entry = LogEntry { + address: try!(d.val_at(0)), + topics: try!(d.val_at(1)), + data: try!(d.val_at(2)), + }; + Ok(entry) + } +} + +impl HeapSizeOf for LogEntry { + fn heap_size_of_children(&self) -> usize { + self.topics.heap_size_of_children() + self.data.heap_size_of_children() + } +} + impl LogEntry { /// Calculates the bloom of this log entry. pub fn bloom(&self) -> LogBloom { diff --git a/ethcore/src/receipt.rs b/ethcore/src/receipt.rs index f43b58224..1ec52c592 100644 --- a/ethcore/src/receipt.rs +++ b/ethcore/src/receipt.rs @@ -55,6 +55,24 @@ impl Encodable for Receipt { } } +impl Decodable for Receipt { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let receipt = Receipt { + state_root: try!(d.val_at(0)), + gas_used: try!(d.val_at(1)), + log_bloom: try!(d.val_at(2)), + logs: try!(d.val_at(3)), + }; + Ok(receipt) + } +} + +impl HeapSizeOf for Receipt { + fn heap_size_of_children(&self) -> usize { + self.logs.heap_size_of_children() + } +} #[test] fn test_basic() { diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 0a2b95e8d..38cd48f32 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -224,7 +224,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { @@ -237,7 +237,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes let temp = RandomTempPath::new(); let bc = BlockChain::new(&create_unverifiable_block(0, H256::zero()), temp.as_path()); for block_order in 1..block_number { - bc.insert_block(&create_unverifiable_block_with_extra(block_order, bc.best_block_hash(), None), &[]); + bc.insert_block(&create_unverifiable_block_with_extra(block_order, bc.best_block_hash(), None), vec![]); } GuardedTempResult:: { diff --git a/ethcore/src/verification.rs b/ethcore/src/verification.rs index 548150f09..ad5efd24a 100644 --- a/ethcore/src/verification.rs +++ b/ethcore/src/verification.rs @@ -306,6 +306,10 @@ mod tests { fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockNumber, _to_block: BlockNumber) -> Vec { unimplemented!() } + + fn block_receipts(&self, _hash: &H256) -> Option { + unimplemented!() + } } fn basic_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {