diff --git a/db/src/block_chain_db.rs b/db/src/block_chain_db.rs index 9ff2eeda..123b9a6f 100644 --- a/db/src/block_chain_db.rs +++ b/db/src/block_chain_db.rs @@ -24,7 +24,7 @@ use storage::{ BlockRef, Error, BlockHeaderProvider, BlockProvider, BlockOrigin, TransactionMeta, IndexedBlockProvider, TransactionMetaProvider, TransactionProvider, TransactionOutputProvider, BlockChain, Store, SideChainOrigin, ForkChain, Forkable, CanonStore, ConfigStore, BestBlock, NullifierTracker, Nullifier, - EpochTag, + EpochTag, RegularTreeState, TreeStateProvider, }; const KEY_BEST_BLOCK_NUMBER: &'static str = "best_block_number"; @@ -205,20 +205,41 @@ impl BlockChainDatabase where T: KeyValueDatabase { return Ok(()) } - let parent_hash = block.header.raw.previous_header_hash.clone(); - if !self.contains_block(parent_hash.clone().into()) && !parent_hash.is_zero() { + let parent_hash = block.header.raw.previous_header_hash; + if !self.contains_block(parent_hash.into()) && !parent_hash.is_zero() { return Err(Error::UnknownParent); } + let mut tree_state = if parent_hash.is_zero() { + RegularTreeState::new() + } else { + self.tree_at_block(&parent_hash) + .expect(&format!("Corrupted database - no root for block {}", parent_hash)) + }; + let mut update = DBTransaction::new(); - update.insert(KeyValue::BlockHeader(block.hash().clone(), block.header.raw)); - let tx_hashes = block.transactions.iter().map(|tx| tx.hash.clone()).collect::>(); - update.insert(KeyValue::BlockTransactions(block.header.hash.clone(), List::from(tx_hashes))); + update.insert(KeyValue::BlockHeader(*block.hash(), block.header.raw)); + let tx_hashes = block.transactions.iter().map(|tx| tx.hash).collect::>(); + update.insert(KeyValue::BlockTransactions(block.header.hash, List::from(tx_hashes))); for tx in block.transactions.into_iter() { + + if let Some(ref js) = tx.raw.join_split { + for js_descriptor in js.descriptions.iter() { + for commitment in &js_descriptor.commitments[..] { + tree_state.append(H256::from(&commitment[..])) + .expect("Appending to a full commitment tree in the block insertion") + } + } + } + update.insert(KeyValue::Transaction(tx.hash, tx.raw)); } + let tree_root = tree_state.root(); + update.insert(KeyValue::BlockRoot(block.header.hash, tree_root)); + update.insert(KeyValue::TreeState(tree_root, tree_state)); + self.db.write(update).map_err(Error::DatabaseError) } @@ -576,6 +597,16 @@ impl NullifierTracker for BlockChainDatabase where T: KeyValueDatabase { } } +impl TreeStateProvider for BlockChainDatabase where T: KeyValueDatabase { + fn tree_at(&self, root: &H256) -> Option { + self.get(Key::TreeRoot(*root)).and_then(Value::as_tree_state) + } + + fn block_root(&self, block_hash: &H256) -> Option { + self.get(Key::BlockRoot(*block_hash)).and_then(Value::as_block_root) + } +} + impl BlockChain for BlockChainDatabase where T: KeyValueDatabase { fn insert(&self, block: IndexedBlock) -> Result<(), Error> { BlockChainDatabase::insert(self, block) diff --git a/db/src/kv/memorydb.rs b/db/src/kv/memorydb.rs index 29d8f19d..4bf5df50 100644 --- a/db/src/kv/memorydb.rs +++ b/db/src/kv/memorydb.rs @@ -7,12 +7,13 @@ use bytes::Bytes; use ser::List; use chain::{Transaction as ChainTransaction, BlockHeader}; use kv::{Transaction, Key, KeyState, Operation, Value, KeyValueDatabase, KeyValue}; -use storage::{TransactionMeta, EpochTag}; +use storage::{TransactionMeta, EpochTag, RegularTreeState, Nullifier}; #[derive(Default, Debug)] struct InnerDatabase { meta: HashMap<&'static str, KeyState>, block_hash: HashMap>, + block_root: HashMap>, block_header: HashMap>, block_transactions: HashMap>>, transaction: HashMap>, @@ -21,6 +22,7 @@ struct InnerDatabase { configuration: HashMap<&'static str, KeyState>, sprout_nullifiers: HashMap>, sapling_nullifiers: HashMap>, + tree_state: HashMap>, } #[derive(Default, Debug)] @@ -55,6 +57,28 @@ impl MemoryDatabase { let configuration = replace(&mut db.configuration, HashMap::default()).into_iter() .flat_map(|(key, state)| state.into_operation(key, KeyValue::Configuration, Key::Configuration)); + let sprout_nullifiers = replace(&mut db.sprout_nullifiers, HashMap::default()).into_iter() + .flat_map(|(key, state)| + state.into_operation(key, + |k, _| KeyValue::Nullifier(Nullifier::new(EpochTag::Sprout, k)), + |h| Key::Nullifier(Nullifier::new(EpochTag::Sprout, h)) + ) + ); + + let sapling_nullifiers = replace(&mut db.sapling_nullifiers, HashMap::default()).into_iter() + .flat_map(|(key, state)| + state.into_operation(key, + |k, _| KeyValue::Nullifier(Nullifier::new(EpochTag::Sapling, k)), + |h| Key::Nullifier(Nullifier::new(EpochTag::Sapling, h)) + ) + ); + + let tree_state = replace(&mut db.tree_state, HashMap::default()).into_iter() + .flat_map(|(key, state)| state.into_operation(key, KeyValue::TreeState, Key::TreeRoot)); + + let block_root = replace(&mut db.block_root, HashMap::default()).into_iter() + .flat_map(|(key, state)| state.into_operation(key, KeyValue::BlockRoot, Key::BlockRoot)); + Transaction { operations: meta .chain(block_hash) @@ -64,6 +88,10 @@ impl MemoryDatabase { .chain(transaction_meta) .chain(block_number) .chain(configuration) + .chain(tree_state) + .chain(block_root) + .chain(sprout_nullifiers) + .chain(sapling_nullifiers) .collect() } } @@ -87,6 +115,8 @@ impl KeyValueDatabase for MemoryDatabase { EpochTag::Sprout => { db.sprout_nullifiers.insert(*key.hash(), KeyState::Insert(())); }, EpochTag::Sapling => { db.sapling_nullifiers.insert(*key.hash(), KeyState::Insert(())); }, }, + KeyValue::TreeState(key, value) => { db.tree_state.insert(key, KeyState::Insert(value)); }, + KeyValue::BlockRoot(key, value) => { db.block_root.insert(key, KeyState::Insert(value)); }, }, Operation::Delete(delete) => match delete { Key::Meta(key) => { db.meta.insert(key, KeyState::Delete); } @@ -101,6 +131,8 @@ impl KeyValueDatabase for MemoryDatabase { EpochTag::Sprout => { db.sprout_nullifiers.insert(*key.hash(), KeyState::Delete); }, EpochTag::Sapling => { db.sapling_nullifiers.insert(*key.hash(), KeyState::Delete); }, }, + Key::TreeRoot(key) => { db.tree_state.insert(key, KeyState::Delete); }, + Key::BlockRoot(key) => { db.block_root.insert(key, KeyState::Delete); }, }, } } @@ -121,7 +153,9 @@ impl KeyValueDatabase for MemoryDatabase { Key::Nullifier(ref key) => match key.tag() { EpochTag::Sprout => db.sprout_nullifiers.get(key.hash()).cloned().unwrap_or_default().map(|_| Value::Empty), EpochTag::Sapling => db.sapling_nullifiers.get(key.hash()).cloned().unwrap_or_default().map(|_| Value::Empty), - } + }, + Key::TreeRoot(ref key) => db.tree_state.get(key).cloned().unwrap_or_default().map(Value::TreeState), + Key::BlockRoot(ref key) => db.block_root.get(key).cloned().unwrap_or_default().map(Value::TreeRoot), }; Ok(result) diff --git a/db/src/kv/transaction.rs b/db/src/kv/transaction.rs index 1aea1969..1b0ac7ec 100644 --- a/db/src/kv/transaction.rs +++ b/db/src/kv/transaction.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use hash::H256; use ser::{serialize, List, deserialize}; use chain::{Transaction as ChainTransaction, BlockHeader}; -use storage::{TransactionMeta, Nullifier, EpochTag}; +use storage::{TransactionMeta, Nullifier, EpochTag, RegularTreeState}; pub const COL_COUNT: u32 = 16; pub const COL_META: u32 = 0; @@ -14,7 +14,9 @@ pub const COL_TRANSACTIONS_META: u32 = 5; pub const COL_BLOCK_NUMBERS: u32 = 6; pub const COL_SPROUT_NULLIFIERS: u32 = 7; pub const COL_SAPLING_NULLIFIERS: u32 = 8; -pub const COL_CONFIGURATION: u32 = 9; +pub const COL_TREESTATES: u32 = 9; +pub const COL_BLOCK_ROOTS: u32 = 10; +pub const COL_CONFIGURATION: u32 = 11; #[derive(Debug)] pub enum Operation { @@ -33,6 +35,8 @@ pub enum KeyValue { BlockNumber(H256, u32), Configuration(&'static str, Bytes), Nullifier(Nullifier), + TreeState(H256, RegularTreeState), + BlockRoot(H256, H256), } #[derive(Debug)] @@ -46,6 +50,8 @@ pub enum Key { BlockNumber(H256), Configuration(&'static str), Nullifier(Nullifier), + TreeRoot(H256), + BlockRoot(H256), } #[derive(Debug, Clone)] @@ -59,6 +65,8 @@ pub enum Value { BlockNumber(u32), Configuration(Bytes), Empty, + TreeState(RegularTreeState), + TreeRoot(H256), } impl Value { @@ -73,6 +81,8 @@ impl Value { Key::BlockNumber(_) => deserialize(bytes).map(Value::BlockNumber), Key::Configuration(_) => deserialize(bytes).map(Value::Configuration), Key::Nullifier(_) => Ok(Value::Empty), + Key::TreeRoot(_) => deserialize(bytes).map(Value::TreeState), + Key::BlockRoot(_) => deserialize(bytes).map(Value::TreeRoot), }.map_err(|e| format!("{:?}", e)) } @@ -131,6 +141,20 @@ impl Value { _ => None, } } + + pub fn as_tree_state(self) -> Option { + match self { + Value::TreeState(tree) => Some(tree), + _ => None, + } + } + + pub fn as_block_root(self) -> Option { + match self { + Value::TreeRoot(v) => Some(v), + _ => None, + } + } } #[derive(Debug, Clone)] @@ -237,6 +261,8 @@ impl<'a> From<&'a KeyValue> for RawKeyValue { EpochTag::Sapling => (COL_SAPLING_NULLIFIERS, serialize(key.hash()), Bytes::new()), }, KeyValue::BlockNumber(ref key, ref value) => (COL_BLOCK_NUMBERS, serialize(key), serialize(value)), + KeyValue::TreeState(ref key, ref value) => (COL_TREESTATES, serialize(key), serialize(value)), + KeyValue::BlockRoot(ref key, ref value) => (COL_BLOCK_ROOTS, serialize(key), serialize(value)), KeyValue::Configuration(ref key, ref value) => (COL_CONFIGURATION, serialize(key), serialize(value)), }; @@ -275,7 +301,9 @@ impl<'a> From<&'a Key> for RawKey { EpochTag::Sprout => (COL_SPROUT_NULLIFIERS, serialize(key.hash())), EpochTag::Sapling => (COL_SAPLING_NULLIFIERS, serialize(key.hash())), }, + Key::TreeRoot(ref key) => (COL_TREESTATES, serialize(key)), Key::BlockNumber(ref key) => (COL_BLOCK_NUMBERS, serialize(key)), + Key::BlockRoot(ref key) => (COL_BLOCK_ROOTS, serialize(key)), Key::Configuration(ref key) => (COL_CONFIGURATION, serialize(key)), }; diff --git a/storage/src/lib.rs b/storage/src/lib.rs index af3482c8..9115458d 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -27,6 +27,7 @@ mod transaction_meta; mod transaction_provider; mod nullifier; mod tree_state; +mod tree_state_provider; pub use primitives::{hash, bytes}; @@ -44,6 +45,7 @@ pub use transaction_meta::TransactionMeta; pub use transaction_provider::{TransactionProvider, TransactionOutputProvider, TransactionMetaProvider}; pub use nullifier::{Nullifier, NullifierTracker}; pub use tree_state::{TreeState, H32 as H32TreeDim, Dim as TreeDim, RegularTreeState}; +pub use tree_state_provider::TreeStateProvider; /// Epoch tag. /// diff --git a/storage/src/tree_state.rs b/storage/src/tree_state.rs index a6ea1673..664233cf 100644 --- a/storage/src/tree_state.rs +++ b/storage/src/tree_state.rs @@ -76,12 +76,14 @@ pub trait Dim { const HEIGHT: usize; } +#[derive(Clone, Debug, PartialEq)] pub struct H32; impl Dim for H32 { const HEIGHT: usize = 32; } +#[derive(Clone, Debug, PartialEq)] pub struct TreeState { _phantom: ::std::marker::PhantomData, left: Option, @@ -145,6 +147,10 @@ impl TreeState { root } + + pub fn empty_root() -> H256 { + EMPTY_ROOTS[D::HEIGHT] + } } pub type RegularTreeState = TreeState; @@ -206,6 +212,25 @@ mod tests { ); } + #[test] + fn empty_32_root() { + assert_eq!( + RegularTreeState::new().root(), + H256::from("ac58cd1388fec290d398f1944b564449a63c815880566bd1d189f7839e3b0c8c"), + ) + } + + #[test] + fn appended_1_32_root() { + let mut tree = RegularTreeState::new(); + tree.append(H256::from("bab6e8992959caf0ca94847c36b4e648a7f88a9b9c6a62ea387cf1fb9badfd62")) + .expect("failed to append to the tree"); + assert_eq!( + tree.root(), + H256::from("af3a29c548af2d8314544875fe0a59555bfda3c81ea78da54bd02f89cce68acb") + ); + } + #[test] fn single_elem_in_double_tree() { let mut tree = TreeState::

::new(); diff --git a/storage/src/tree_state_provider.rs b/storage/src/tree_state_provider.rs new file mode 100644 index 00000000..d02fc3be --- /dev/null +++ b/storage/src/tree_state_provider.rs @@ -0,0 +1,13 @@ +use hash::H256; +use bytes::Bytes; +use RegularTreeState; + +pub trait TreeStateProvider { + fn tree_at(&self, root: &H256) -> Option; + + fn block_root(&self, block_hash: &H256) -> Option; + + fn tree_at_block(&self, block_hash: &H256) -> Option { + self.block_root(block_hash).and_then(|h| self.tree_at(&h)) + } +}