diff --git a/Cargo.lock b/Cargo.lock index f4fb1f11..30cf85d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,11 @@ name = "base58" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bit-vec" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitcrypto" version = "0.1.0" @@ -115,6 +120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "db" version = "0.1.0" dependencies = [ + "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "chain 0.1.0", "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -705,6 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d89f1b0e242270b5b797778af0c8d182a1a2ccac5d8d6fadf414223cc0fab096" "checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" diff --git a/db/Cargo.toml b/db/Cargo.toml index 800fdc32..a1b72dfa 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -13,6 +13,7 @@ chain = { path = "../chain" } serialization = { path = "../serialization" } parking_lot = "0.3" test-data = { path = "../test-data" } +bit-vec = "0.4" [features] dev = [] diff --git a/db/src/kvdb.rs b/db/src/kvdb.rs index e0596a9d..dc5790ac 100644 --- a/db/src/kvdb.rs +++ b/db/src/kvdb.rs @@ -9,6 +9,7 @@ use rocksdb::{DB, Writable, WriteBatch, WriteOptions, IteratorMode, DBIterator, use elastic_array::ElasticArray32; use parking_lot::RwLock; use primitives::bytes::Bytes; +use byteorder::{LittleEndian, ByteOrder}; /// Database error pub enum Error { @@ -89,6 +90,20 @@ impl DBTransaction { key: ekey, }); } + + /// Write u64 + pub fn write_u64(&mut self, col: Option, key: &[u8], value: u64) { + let mut val = [0u8; 8]; + LittleEndian::write_u64(&mut val, value); + self.put(col, key, &val); + } + + /// Write u32 + pub fn write_u32(&mut self, col: Option, key: &[u8], value: u32) { + let mut val = [0u8; 4]; + LittleEndian::write_u32(&mut val, value); + self.put(col, key, &val); + } } enum KeyState { diff --git a/db/src/lib.rs b/db/src/lib.rs index 8b892a20..4d34a4cd 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -7,6 +7,7 @@ extern crate primitives; extern crate byteorder; extern crate chain; extern crate serialization; +extern crate bit_vec; #[cfg(test)] extern crate ethcore_devtools as devtools; @@ -17,9 +18,10 @@ mod kvdb; mod storage; #[cfg(feature="dev")] mod test_storage; +mod transaction_meta; pub enum BlockRef { - Number(u64), + Number(u32), Hash(primitives::hash::H256), } diff --git a/db/src/storage.rs b/db/src/storage.rs index c661dac2..47164197 100644 --- a/db/src/storage.rs +++ b/db/src/storage.rs @@ -9,6 +9,7 @@ use primitives::bytes::Bytes; use super::BlockRef; use serialization; use chain::{self, RepresentH256}; +use parking_lot::RwLock; const COL_COUNT: u32 = 10; const COL_META: u32 = 0; @@ -28,10 +29,10 @@ const DB_VERSION: u32 = 1; /// Blockchain storage interface pub trait Store : Send + Sync { /// get best block number - fn best_block_number(&self) -> Option; + fn best_block_number(&self) -> Option; /// resolves hash by block number - fn block_hash(&self, number: u64) -> Option; + fn block_hash(&self, number: u32) -> Option; /// resolves header bytes by block reference (number/hash) fn block_header_bytes(&self, block_ref: BlockRef) -> Option; @@ -63,6 +64,8 @@ pub trait Store : Send + Sync { /// Blockchain storage with rocksdb database pub struct Storage { database: Database, + best_block_number: RwLock>, + best_block_hash: RwLock>, } #[derive(Debug)] @@ -93,13 +96,15 @@ impl From for Error { } } -fn u64_key(num: u64) -> [u8; 8] { - let mut result = [0u8; 8]; - LittleEndian::write_u64(&mut result, num); +fn u32_key(num: u32) -> [u8; 4] { + let mut result = [0u8; 4]; + LittleEndian::write_u32(&mut result, num); result } const KEY_VERSION: &'static[u8] = b"version"; +const KEY_BEST_BLOCK_NUMBER: &'static[u8] = b"best_block_number"; +const KEY_BEST_BLOCK_HASH: &'static[u8] = b"best_block_hash"; impl Storage { @@ -110,25 +115,41 @@ impl Storage { let cfg = DatabaseConfig::with_columns(Some(COL_COUNT)); let db = try!(Database::open(&cfg, &*path.as_ref().to_string_lossy())); - match try!(db.get(Some(COL_META), KEY_VERSION)) { - Some(val) => { - let ver = LittleEndian::read_u32(&val); - if ver == DB_VERSION { - Ok(Storage { database: db, }) - } - else { - Err(Error::Meta(MetaError::UnsupportedVersion)) + let storage = Storage { + database: db, + best_block_number: RwLock::default(), + best_block_hash: RwLock::default(), + }; + + match storage.read_meta_u32(KEY_VERSION) { + Some(ver) => { + if ver != DB_VERSION { + return Err(Error::Meta(MetaError::UnsupportedVersion)) } }, _ => { - let mut meta_transaction = db.transaction(); - let mut ver_val = [0u8; 4]; - LittleEndian::write_u32(&mut ver_val, DB_VERSION); - meta_transaction.put(Some(COL_META), KEY_VERSION, &ver_val); - try!(db.write(meta_transaction)); - Ok(Storage { database: db, }) - } - } + let mut meta_transaction = storage.database.transaction(); + meta_transaction.write_u32(Some(COL_META), KEY_VERSION, DB_VERSION); + try!(storage.database.write(meta_transaction)); + }, + }; + + *storage.best_block_number.write() = storage.read_meta_u32(KEY_BEST_BLOCK_NUMBER); + *storage.best_block_hash.write() = storage.get(COL_META, KEY_BEST_BLOCK_HASH).map(|val| H256::from(&**val)); + + Ok(storage) + } + + fn read_meta(&self, key: &[u8]) -> Option { + self.get(COL_META, key) + } + + fn read_meta_u64(&self, key: &[u8]) -> Option { + self.read_meta(key).map(|val| LittleEndian::read_u64(&val)) + } + + fn read_meta_u32(&self, key: &[u8]) -> Option { + self.read_meta(key).map(|val| LittleEndian::read_u32(&val)) } /// is invoked on database non-fatal query errors @@ -186,12 +207,12 @@ impl Storage { } impl Store for Storage { - fn best_block_number(&self) -> Option { - unimplemented!() + fn best_block_number(&self) -> Option { + *self.best_block_number.read() } - fn block_hash(&self, number: u64) -> Option { - self.get(COL_BLOCK_HASHES, &u64_key(number)) + fn block_hash(&self, number: u32) -> Option { + self.get(COL_BLOCK_HASHES, &u32_key(number)) .map(|val| H256::from(&**val)) } @@ -233,6 +254,8 @@ impl Store for Storage { } fn insert_block(&self, block: &chain::Block) -> Result<(), Error> { + let best_block_number = self.best_block_number.write(); + let mut transaction = self.database.transaction(); let tx_space = block.transactions().len() * 32; @@ -308,7 +331,6 @@ mod tests { let loaded_transaction = store.transaction(&tx1).unwrap(); assert_eq!(loaded_transaction.hash(), block.transactions()[0].hash()); - } } diff --git a/db/src/test_storage.rs b/db/src/test_storage.rs index 0a0463bc..d616f988 100644 --- a/db/src/test_storage.rs +++ b/db/src/test_storage.rs @@ -54,11 +54,11 @@ impl TestStorage { } impl Store for TestStorage { - fn best_block_number(&self) -> Option { - self.data.read().best_block_number.map(|b| b as u64) + fn best_block_number(&self) -> Option { + self.data.read().best_block_number.map(|b| b as u32) } - fn block_hash(&self, number: u64) -> Option { + fn block_hash(&self, number: u32) -> Option { let data = self.data.read(); data.heights.get(&(number as usize)).cloned() } diff --git a/db/src/transaction_meta.rs b/db/src/transaction_meta.rs new file mode 100644 index 00000000..aa6a8221 --- /dev/null +++ b/db/src/transaction_meta.rs @@ -0,0 +1,46 @@ +//! Transaction index + +use bit_vec::BitVec; +use byteorder::{LittleEndian, ByteOrder}; + +/// structure for indexing transaction info +pub struct TransactionMeta { + block_height: u32, + spent: BitVec, +} + +pub enum Error { + KeyTooShort(usize), +} + +impl TransactionMeta { + /// new transaction description for indexing + pub fn new(block_height: u32, outputs: usize) -> Self { + TransactionMeta { + block_height: block_height, + spent: BitVec::from_elem(outputs, false), + } + } + + /// note that particular output has been used + pub fn note_used(&mut self, index: usize) { + self.spent.set(index, true); + } + + pub fn to_bytes(self) -> Vec { + let mut result = vec![0u8; 4]; + LittleEndian::write_u32(&mut result[0..4], self.block_height); + result.extend(self.spent.to_bytes()); + result + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() <= 4 { return Err(Error::KeyTooShort(bytes.len())); } + + Ok(TransactionMeta { + block_height: LittleEndian::read_u32(&bytes[0..4]), + spent: BitVec::from_bytes(&bytes[5..]), + }) + } + +}