diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 5dffc9ec371..25bf2ecd1b1 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -40,6 +40,7 @@ use types::blockchain_info::BlockChainInfo; use types::tree_route::TreeRoute; use blockchain::update::ExtrasUpdate; use blockchain::{CacheSize, ImportRoute, Config}; +use error::MetadataError; use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; use encoded; @@ -508,6 +509,8 @@ impl BlockChain { total_difficulty: header.difficulty(), parent: header.parent_hash(), children: vec![], + finalized: false, + metadata: HashMap::new(), }; let mut batch = DBTransaction::new(); @@ -647,7 +650,9 @@ impl BlockChain { /// `None` is returned. pub fn tree_route(&self, from: H256, to: H256) -> Option { let mut from_branch = vec![]; + let mut is_from_route_finalized = false; let mut to_branch = vec![]; + let mut is_to_route_finalized = false; let mut from_details = self.block_details(&from)?; let mut to_details = self.block_details(&to)?; @@ -659,12 +664,14 @@ impl BlockChain { from_branch.push(current_from); current_from = from_details.parent.clone(); from_details = self.block_details(&from_details.parent)?; + is_from_route_finalized = is_from_route_finalized || from_details.finalized; } while to_details.number > from_details.number { to_branch.push(current_to); current_to = to_details.parent.clone(); to_details = self.block_details(&to_details.parent)?; + is_to_route_finalized = is_to_route_finalized || to_details.finalized; } assert_eq!(from_details.number, to_details.number); @@ -674,10 +681,12 @@ impl BlockChain { from_branch.push(current_from); current_from = from_details.parent.clone(); from_details = self.block_details(&from_details.parent)?; + is_from_route_finalized = is_from_route_finalized || from_details.finalized; to_branch.push(current_to); current_to = to_details.parent.clone(); to_details = self.block_details(&to_details.parent)?; + is_to_route_finalized = is_to_route_finalized || to_details.finalized; } let index = from_branch.len(); @@ -687,7 +696,9 @@ impl BlockChain { Some(TreeRoute { blocks: from_branch, ancestor: current_from, - index: index + index: index, + is_from_route_finalized: is_from_route_finalized, + is_to_route_finalized: is_to_route_finalized, }) } @@ -772,6 +783,8 @@ impl BlockChain { total_difficulty: info.total_difficulty, parent: header.parent_hash(), children: Vec::new(), + finalized: false, + metadata: HashMap::new(), }; let mut update = HashMap::new(); @@ -961,16 +974,20 @@ impl BlockChain { assert_eq!(number, parent_details.number + 1); - match route.blocks.len() { - 0 => BlockLocation::CanonChain, - _ => { - let retracted = route.blocks.iter().take(route.index).cloned().collect::>().into_iter().collect::>(); - let enacted = route.blocks.into_iter().skip(route.index).collect::>(); - BlockLocation::BranchBecomingCanonChain(BranchBecomingCanonChainData { - ancestor: route.ancestor, - enacted: enacted, - retracted: retracted, - }) + if route.is_from_route_finalized { + BlockLocation::Branch + } else { + match route.blocks.len() { + 0 => BlockLocation::CanonChain, + _ => { + let retracted = route.blocks.iter().take(route.index).cloned().collect::>().into_iter().collect::>(); + let enacted = route.blocks.into_iter().skip(route.index).collect::>(); + BlockLocation::BranchBecomingCanonChain(BranchBecomingCanonChainData { + ancestor: route.ancestor, + enacted: enacted, + retracted: retracted, + }) + } } } } else { @@ -979,6 +996,37 @@ impl BlockChain { } } + /// Mark a block to be considered finalized. + pub fn mark_finalized(&self, batch: &mut DBTransaction, block_hash: H256) -> Result<(), MetadataError> { + let mut block_details = self.block_details(&block_hash).ok_or(MetadataError::UnknownBlock)?; + block_details.finalized = true; + + self.update_block_details(batch, block_hash, block_details); + Ok(()) + } + + /// Update metadata detail for an existing block. + pub fn update_metadata>>(&self, batch: &mut DBTransaction, block_hash: H256, metadata: T) -> Result<(), MetadataError> { + let mut block_details = self.block_details(&block_hash).ok_or(MetadataError::UnknownBlock)?; + let metadata: HashMap = metadata.into(); + for (key, value) in metadata { + block_details.metadata.insert(key, value); + } + + self.update_block_details(batch, block_hash, block_details); + Ok(()) + } + + /// Prepares extras block detail update. + fn update_block_details(&self, batch: &mut DBTransaction, block_hash: H256, block_details: BlockDetails) { + let mut details_map = HashMap::new(); + details_map.insert(block_hash, block_details); + + // We're only updating one existing value. So it shouldn't suffer from cache decoherence problem. + let mut write_details = self.pending_block_details.write(); + batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, details_map, CacheUpdatePolicy::Overwrite); + } + /// Prepares extras update. fn prepare_update(&self, batch: &mut DBTransaction, update: ExtrasUpdate, is_best: bool) { @@ -1179,6 +1227,8 @@ impl BlockChain { total_difficulty: info.total_difficulty, parent: parent_hash, children: vec![], + finalized: false, + metadata: HashMap::new(), }; // write to batch diff --git a/ethcore/src/blockchain/extras.rs b/ethcore/src/blockchain/extras.rs index 97583a693a6..2444fa7d971 100644 --- a/ethcore/src/blockchain/extras.rs +++ b/ethcore/src/blockchain/extras.rs @@ -18,11 +18,14 @@ use std::ops; use std::io::Write; +use std::collections::HashMap; use blooms::{GroupPosition, BloomGroup}; use db::Key; use engines::epoch::{Transition as EpochTransition}; use header::BlockNumber; use receipt::Receipt; +use rlp; +use bytes::Bytes; use heapsize::HeapSizeOf; use ethereum_types::{H256, H264, U256}; @@ -167,7 +170,7 @@ impl Key for u64 { } /// Familial details concerning a block -#[derive(Debug, Clone, RlpEncodable, RlpDecodable)] +#[derive(Debug, Clone)] pub struct BlockDetails { /// Block number pub number: BlockNumber, @@ -177,6 +180,63 @@ pub struct BlockDetails { pub parent: H256, /// List of children block hashes pub children: Vec, + /// Whether the block is considered finalized + pub finalized: bool, + /// Metadata information + pub metadata: HashMap, +} + +impl rlp::Encodable for BlockDetails { + fn rlp_append(&self, stream: &mut rlp::RlpStream) { + let use_short_version = self.metadata.len() == 0 && !self.finalized; + + match use_short_version { + true => { stream.begin_list(4); }, + false => { stream.begin_list(6); }, + } + + stream.append(&self.number); + stream.append(&self.total_difficulty); + stream.append(&self.parent); + stream.append_list(&self.children); + if !use_short_version { + stream.append(&self.finalized); + + let metadata: Vec = self.metadata.clone().into_iter().map(|(key, value)| { + BlockMetadata { key, value } + }).collect(); + stream.append_list(&metadata); + } + } +} + +impl rlp::Decodable for BlockDetails { + fn decode(rlp: &rlp::UntrustedRlp) -> Result { + let use_short_version = match rlp.item_count()? { + 4 => true, + 6 => false, + _ => return Err(rlp::DecoderError::RlpIncorrectListLen), + }; + + Ok(BlockDetails { + number: rlp.val_at(0)?, + total_difficulty: rlp.val_at(1)?, + parent: rlp.val_at(2)?, + children: rlp.list_at(3)?, + finalized: if use_short_version { + false + } else { + rlp.val_at(4)? + }, + metadata: if use_short_version { + HashMap::new() + } else { + let metadatas: Vec = rlp.list_at(5)?; + + metadatas.into_iter().map(|metadata| (metadata.key, metadata.value)).collect() + }, + }) + } } impl HeapSizeOf for BlockDetails { @@ -185,6 +245,15 @@ impl HeapSizeOf for BlockDetails { } } +/// Metadata key and value +#[derive(Debug, Clone, RlpEncodable, RlpDecodable)] +struct BlockMetadata { + /// Key of the metadata + pub key: Bytes, + /// Value of the metadata + pub value: Bytes, +} + /// Represents address of certain transaction within block #[derive(Debug, PartialEq, Clone, RlpEncodable, RlpDecodable)] pub struct TransactionAddress { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 02067afd002..f96a4b3e8e0 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -742,7 +742,9 @@ impl BlockChainClient for TestBlockChainClient { } } if adding { Vec::new() } else { blocks } - } + }, + is_from_route_finalized: false, + is_to_route_finalized: false, }) } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 4c8157a82f1..527bd90c619 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -147,6 +147,23 @@ impl fmt::Display for BlockError { } } +#[derive(Debug, Clone, Copy, PartialEq)] +/// Errors related to metadata operations +pub enum MetadataError { + /// The metadata block trying to set is unknown. + UnknownBlock, +} + +impl fmt::Display for MetadataError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let msg = match *self { + MetadataError::UnknownBlock => "unknown block", + }; + + f.write_fmt(format_args!("Block metadata error ({})", msg)) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] /// Import to the block queue result pub enum ImportError { @@ -247,6 +264,8 @@ pub enum Error { Ethkey(EthkeyError), /// Account Provider error. AccountProvider(AccountsError), + /// Block metadata error. + Metadata(MetadataError), } impl fmt::Display for Error { @@ -271,6 +290,7 @@ impl fmt::Display for Error { Error::Engine(ref err) => err.fmt(f), Error::Ethkey(ref err) => err.fmt(f), Error::AccountProvider(ref err) => err.fmt(f), + Error::Metadata(ref err) => err.fmt(f), } } } @@ -285,6 +305,12 @@ impl error::Error for Error { /// Result of import block operation. pub type ImportResult = Result; +impl From for Error { + fn from(err: MetadataError) -> Error { + Error::Metadata(err) + } +} + impl From for Error { fn from(err: ClientError) -> Error { match err { diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 5b0700bfd9c..df9522d95c9 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -454,6 +454,8 @@ mod tests { total_difficulty: header.difficulty().clone(), parent: header.parent_hash().clone(), children: Vec::new(), + finalized: false, + metadata: Default::default(), } }) } diff --git a/ethcore/types/src/tree_route.rs b/ethcore/types/src/tree_route.rs index b3fe431ab99..4df6c2a7091 100644 --- a/ethcore/types/src/tree_route.rs +++ b/ethcore/types/src/tree_route.rs @@ -27,4 +27,8 @@ pub struct TreeRoute { pub ancestor: H256, /// An index where best common ancestor would be. pub index: usize, + /// Whether it has finalized blocks from `from` (inclusive) to `ancestor` (exclusive). + pub is_from_route_finalized: bool, + /// Whether it has finalized blocks from `ancestor` (exclusive) to `to` (inclusive). + pub is_to_route_finalized: bool, }