diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 151be12..cd25f85 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ "main" ] + branches: [ "main", "dev", "rewrite" ] pull_request: - branches: [ "main", "dev" ] + branches: [ "main", "dev", "rewrite" ] env: CARGO_TERM_COLOR: always @@ -21,4 +21,4 @@ jobs: - name: Check format code run: cargo fmt -- --check - name: Clippy - run: cargo clippy -- -D warnings \ No newline at end of file + run: cargo clippy -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index 0e814fa..9d37778 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,26 +6,29 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -base64 = "0.13" -byteorder = "1.2.7" -colored = ">=2" -env_logger = "0.9.0" -error-stack = "0.3.1" +base64 = "0.22.1" +byteorder = "1.5.0" +colored = "2.1.0" +env_logger = "0.11.3" +error-stack = "0.4.1" hex = "0.4.3" lazy_static = "1.4.0" -log = "0.4.17" -num-bigint = "0.4" -num-traits = "0.2" -rsa = "0.5" -secp256k1 = { version = "0.22.1", features = ["rand-std","bitcoin_hashes"] } -sha2 = "0.9.5" +log = "0.4.21" +num-bigint = "0.4.4" +num-traits = "0.2.19" +rsa = "0.9.6" +secp256k1 = { version = "0.29.0", features = ["rand-std"] } +sha2 = "0.10.8" sled = "0.34.7" -thiserror = "1.0" -tokio = { version = "1", features = ["full"] } -zstd = "0.9" +thiserror = "1.0.59" +tokio = { version = "1.37.0", features = ["full"] } +zstd = "0.13.1" +primitive-types = "0.12.2" +async-trait = "0.1.80" +parking_lot = "0.12.2" [dev-dependencies] rand = "0.8.5" -[profile.test] +[profile.test] opt-level = 3 diff --git a/examples/mine.rs b/examples/mine.rs new file mode 100644 index 0000000..2b40c03 --- /dev/null +++ b/examples/mine.rs @@ -0,0 +1,73 @@ +use blockchaintree::static_values::BLOCKS_PER_EPOCH; +use blockchaintree::tools; +use blockchaintree::{blockchaintree::BlockChainTree, static_values}; +use primitive_types::U256; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn main() { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let mut tree = BlockChainTree::new().unwrap(); + + let main_chain = tree.get_main_chain(); + + let wallet = [1u8; 33]; + + loop { + println!("Current height: {}", main_chain.get_height()); + println!( + "Current miner balance: {}", + tree.get_amount(&wallet).unwrap() + ); + println!( + "Current root balance: {}", + tree.get_amount(&static_values::ROOT_PUBLIC_ADDRESS) + .unwrap() + ); + let mut nonce = U256::zero(); + let last_block = main_chain.get_last_block().unwrap().unwrap(); + let prev_hash = last_block.hash().unwrap(); + let difficulty = last_block.get_info().difficulty; + println!( + "Current difficulty: {}", + tools::count_leading_zeros(&difficulty) + ); + while nonce < U256::MAX { + let mut pow = [0u8; 32]; + nonce.to_big_endian(&mut pow); + if tools::check_pow(&prev_hash, &difficulty, &pow) { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + println!("Found nonce! {}", nonce); + + let transactions: &[[u8; 32]] = + if ((last_block.get_info().height + 1) % BLOCKS_PER_EPOCH).is_zero() { + println!("Cycle ended!"); + &[] + } else { + &[[25u8; 32]] + }; + + let block = rt + .block_on(tree.emmit_new_main_block(&pow, &wallet, transactions, timestamp)) + .unwrap(); + + tree.send_amount( + &static_values::ROOT_PUBLIC_ADDRESS, + &wallet, + *static_values::MAIN_CHAIN_PAYMENT, + ) + .unwrap(); + + println!("Added new block! {:?}\n", block.hash().unwrap()); + + rt.block_on(tree.flush()).unwrap(); + break; + } + nonce += U256::one(); + } + } +} diff --git a/examples/mine_derivative.rs b/examples/mine_derivative.rs new file mode 100644 index 0000000..61ed49b --- /dev/null +++ b/examples/mine_derivative.rs @@ -0,0 +1,74 @@ +use blockchaintree::block::Block as _; +use blockchaintree::tools; +use blockchaintree::{blockchaintree::BlockChainTree, static_values}; +use primitive_types::U256; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn main() { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let mut tree = BlockChainTree::new().unwrap(); + + let wallet = [1u8; 33]; + + let chain = tree.get_derivative_chain(&wallet).unwrap(); + + loop { + println!("Current height: {}", chain.get_height()); + println!( + "Current miner gas amount: {}", + tree.get_gas(&wallet).unwrap() + ); + let mut nonce = U256::zero(); + let (prev_hash, difficulty, _prev_timestamp, _height) = + if let Some(block) = chain.get_last_block().unwrap() { + ( + block.hash().unwrap(), + block.get_info().difficulty, + block.get_info().timestamp, + block.get_info().height, + ) + } else { + let block = tree + .get_main_chain() + .find_by_hash(&chain.genesis_hash) + .unwrap() + .unwrap(); + ( + block.hash().unwrap(), + static_values::BEGINNING_DIFFICULTY, + block.get_info().timestamp, + U256::zero(), + ) + }; + println!( + "Current difficulty: {}", + tools::count_leading_zeros(&difficulty) + ); + while nonce < U256::MAX { + let mut pow = [0u8; 32]; + nonce.to_big_endian(&mut pow); + if tools::check_pow(&prev_hash, &difficulty, &pow) { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + println!("Found nonce! {}", nonce); + + let block = rt + .block_on(tree.emmit_new_derivative_block(&pow, &wallet, timestamp)) + .unwrap(); + + tree.add_gas(&wallet, *static_values::MAIN_CHAIN_PAYMENT) + .unwrap(); + + println!("Added new block! {:?}\n", block.hash().unwrap()); + + rt.block_on(chain.flush()).unwrap(); + break; + } + nonce += U256::one(); + } + } +} diff --git a/gen_test_data.py b/gen_test_data.py new file mode 100644 index 0000000..2bdee5e --- /dev/null +++ b/gen_test_data.py @@ -0,0 +1,51 @@ +import hashlib +import os + + +def leading_zeros(num): + if num == 0: + return 8 + + leading_zeros = 0 + while num & 0b10000000 == 0: + leading_zeros += 1 + num = num << 1 + num = num & 0b11111111 + return leading_zeros + + +def total_leading_zeros(hash): + to_return = 0 + for byte in hash: + l_zeros = leading_zeros(byte) + to_return += l_zeros + if l_zeros < 8: + break + + return to_return + +def to_hex_list(bt: bytes) -> list: + r = [] + for b in bt: + r.append(f'0x{hex(b)[2:].upper()}') + return r + +def gen(hash, difficulty): + difficulty = total_leading_zeros(difficulty) + for i in range(1000): + pow = b'' + os.urandom(10) + hasher = hashlib.sha256() + hasher.update(hash) + hasher.update(pow) + + generated_hash = hasher.digest() + ghash_leadin_zeros = total_leading_zeros(generated_hash) + + if ghash_leadin_zeros >= difficulty: + print(', '.join(to_hex_list(pow)), True) + else: + print(', '.join(to_hex_list(pow)), False) + + +gen(hashlib.sha256(b'text').digest(), + b'\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF') diff --git a/src/block.rs b/src/block.rs index b396ccc..4c1e079 100644 --- a/src/block.rs +++ b/src/block.rs @@ -1,22 +1,21 @@ -use crate::blockchaintree::{ - BEGINNING_DIFFICULTY, GENESIS_BLOCK, INCEPTION_TIMESTAMP, ROOT_PUBLIC_ADDRESS, -}; +// use crate::blockchaintree::{ +// BEGINNING_DIFFICULTY, GENESIS_BLOCK, INCEPTION_TIMESTAMP, ROOT_PUBLIC_ADDRESS, +// }; use crate::dump_headers::Headers; use crate::errors::*; -use crate::merkletree::MerkleTree; +use crate::merkletree; use crate::tools; -use crate::transaction::{Transaction, Transactionable}; +use crate::tools::check_pow; +use crate::tools::recalculate_difficulty; +use crate::types::{Address, Hash}; use byteorder::{BigEndian, ReadBytesExt}; -use num_bigint::BigUint; +use error_stack::{Report, Result, ResultExt}; +use primitive_types::U256; use std::cmp::Ordering; use std::convert::TryInto; -use std::mem::transmute; use std::sync::Arc; -use error_stack::{Report, Result, ResultExt}; - #[macro_export] - macro_rules! bytes_to_u64 { ($buffer:expr,$buffer_index:expr) => { (&$buffer[$buffer_index..$buffer_index + 8]) @@ -28,21 +27,21 @@ macro_rules! bytes_to_u64 { #[derive(Debug, Clone)] pub struct BasicInfo { pub timestamp: u64, - pub pow: Vec, - pub previous_hash: [u8; 32], - pub height: u64, - pub difficulty: [u8; 32], - pub founder: [u8; 33], + pub pow: [u8; 32], + pub previous_hash: Hash, + pub height: U256, + pub difficulty: Hash, + pub founder: Address, } impl BasicInfo { pub fn new( timestamp: u64, - pow: Vec, - previous_hash: [u8; 32], - height: u64, - difficulty: [u8; 32], - founder: [u8; 33], + pow: [u8; 32], + previous_hash: Hash, + height: U256, + difficulty: Hash, + founder: Address, ) -> BasicInfo { BasicInfo { timestamp, @@ -55,7 +54,7 @@ impl BasicInfo { } pub fn get_dump_size(&self) -> usize { - 8 + self.pow.len() + 32 + 32 + 8 + 33 + 1 + 8 + 32 + 32 + tools::u256_size(&self.height) + 32 + 33 } pub fn dump(&self, buffer: &mut Vec) -> Result<(), BlockError> { // dumping timestamp @@ -68,10 +67,8 @@ impl BasicInfo { buffer.push(*byte); } - // dumping height - for byte in self.height.to_be_bytes().iter() { - buffer.push(*byte); - } + // dumping pow + buffer.extend(self.pow); // dumping difficulty buffer.extend(self.difficulty); @@ -79,11 +76,8 @@ impl BasicInfo { // dumping founder buffer.extend(self.founder); - // dumping PoW - // tools::dump_biguint(&self.pow, buffer) - // .change_context(BlockError::BasicInfo(BasicInfoErrorKind::Dump))?; - buffer.push(self.pow.len() as u8); - buffer.extend(self.pow.iter()); + // dumping height + tools::dump_u256(&self.height, buffer).unwrap(); Ok(()) } @@ -91,10 +85,10 @@ impl BasicInfo { pub fn parse(data: &[u8]) -> Result { let mut index: usize = 0; - if data.len() <= 113 { + if data.len() <= 105 { return Err( Report::new(BlockError::BasicInfo(BasicInfoErrorKind::Parse)) - .attach_printable("data <= 113"), + .attach_printable("data <= 105"), ); } @@ -103,28 +97,25 @@ impl BasicInfo { index += 8; // parsing previous hash - let previous_hash: [u8; 32] = - unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; + let previous_hash: Hash = unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; index += 32; - // parsing height - let height: u64 = bytes_to_u64!(data, index); - index += 8; + // parsing difficulty + let pow: Hash = unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; + index += 32; // parsing difficulty - let difficulty: [u8; 32] = unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; + let difficulty: Hash = unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; index += 32; //parsing founder - let founder: [u8; 33] = unsafe { data[index..index + 33].try_into().unwrap_unchecked() }; + let founder: Address = unsafe { data[index..index + 33].try_into().unwrap_unchecked() }; index += 33; - // parsing PoW - // let (pow, _) = tools::load_biguint(&data[index..]) - // .change_context(BlockError::BasicInfo(BasicInfoErrorKind::Parse)) - // .attach_printable("failed to parse PoW")?; - let pow_length = data[index]; - let pow = data[index + 1..index + 1 + pow_length as usize].to_vec(); + // parsing height + let (height, _) = tools::load_u256(&data[index..]) + .change_context(BlockError::BasicInfo(BasicInfoErrorKind::Parse))?; + //index += height_size + 1; Ok(BasicInfo { timestamp, @@ -139,468 +130,301 @@ impl BasicInfo { #[derive(Debug)] pub struct TransactionBlock { - transactions: Arc>, - fee: BigUint, - merkle_tree: Option, - merkle_tree_root: [u8; 32], - default_info: BasicInfo, + pub fee: U256, + pub merkle_tree_root: Hash, + pub default_info: BasicInfo, + pub transactions: Vec, } impl TransactionBlock { pub fn new( - transactions: Vec<[u8; 32]>, - fee: BigUint, + fee: U256, default_info: BasicInfo, - merkle_tree_root: [u8; 32], + merkle_tree_root: Hash, + transactions: Vec, ) -> TransactionBlock { TransactionBlock { - transactions: Arc::new(transactions), fee, - merkle_tree: None, default_info, merkle_tree_root, + transactions, } } - pub fn merkle_tree_is_built(&self) -> bool { - self.merkle_tree.is_some() - } - - pub fn build_merkle_tree(&mut self) -> Result<(), BlockError> { - let new_merkle_tree = MerkleTree::build_tree(&self.transactions); - - // let res = new_merkle_tree.add_objects(&self.transactions); - // if !res { - // return Err(Report::new(BlockError::TransactionBlock( - // TxBlockErrorKind::BuildingMerkleTree, - // ))); - // } - self.merkle_tree = Some(new_merkle_tree); - Ok(()) - } - - pub fn check_merkle_tree(&mut self) -> Result { - // build merkle tree if not built - if !self.merkle_tree_is_built() { - self.build_merkle_tree()?; - } - - // transmute computed root into 4 u64 bytes - let constructed_tree_root_raw = self.merkle_tree.as_ref().unwrap().get_root(); - let constructed_tree_root_raw_root: &[u64; 4] = - unsafe { transmute(constructed_tree_root_raw) }; - - // transmute root into 4 u64 bytes - let root: &[u64; 4] = unsafe { transmute(&self.merkle_tree_root) }; - - for (a, b) in root.iter().zip(constructed_tree_root_raw_root.iter()) { - if *a != *b { - return Ok(false); - } - } - Ok(true) - } - pub fn get_dump_size(&self) -> usize { - let mut size: usize = 1; - size += tools::bigint_size(&self.fee); - size += 32; - size += self.default_info.get_dump_size(); - size += self.transactions.len() * 32; - - size + 1 + tools::u256_size(&self.fee) + + 32 + + self.default_info.get_dump_size() + + self.transactions.len() * 32 } - pub fn dump_with_transactions( - &self, - transactions: &[impl Transactionable], - ) -> Result, BlockError> { - let size: usize = self.get_dump_size(); + pub fn dump(&self) -> Result, BlockError> { + let size = self.get_dump_size(); - let mut to_return: Vec = Vec::with_capacity(size); + let mut to_return = Vec::::with_capacity(size); - //header + // header to_return.push(Headers::TransactionBlock as u8); - // merkle tree root + // merkle root to_return.extend(self.merkle_tree_root.iter()); // default info self.default_info .dump(&mut to_return) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump))?; + .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump)) + .attach_printable("Error dumping default info")?; // fee - tools::dump_biguint(&self.fee, &mut to_return) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump))?; - - // amount of transactions - let amount_of_transactions = if self.transactions.len() > 0xFFFF { - return Err( - Report::new(BlockError::TransactionBlock(TxBlockErrorKind::Dump)) - .attach_printable(format!("transactions: {}", self.transactions.len())), - ); - } else { - self.transactions.len() as u16 - }; - - to_return.extend(amount_of_transactions.to_be_bytes().iter()); + tools::dump_u256(&self.fee, &mut to_return) + .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump)) + .attach_printable("Error dumping fee")?; - // transactions/tokens - for transaction in transactions.iter() { - // size of transaction - let size_of_transaction: u32 = transaction.get_dump_size() as u32; - to_return.extend(size_of_transaction.to_be_bytes().iter()); - - for byte in transaction.dump().unwrap().iter() { - to_return.push(*byte); - } + // transactions + for transaction in self.transactions.iter() { + to_return.extend(transaction.iter()); } Ok(to_return) } - pub fn dump(&self) -> Result, BlockError> { - let size: usize = self.get_dump_size(); - - let mut to_return: Vec = Vec::with_capacity(size); + pub fn parse(data: &[u8]) -> Result { + let mut index: usize = 0; - //header - to_return.push(Headers::TransactionBlock as u8); + let merkle_tree_root: Hash = unsafe { data[0..32].try_into().unwrap_unchecked() }; + index += 32; - // merkle tree root - to_return.extend(self.merkle_tree_root); + let default_info = BasicInfo::parse(&data[index..]) + .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse)) + .attach_printable("Error parsing default data")?; + index += default_info.get_dump_size(); - // default info - self.default_info - .dump(&mut to_return) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump))?; + let (fee, fee_size) = tools::load_u256(&data[index..]) + .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse)) + .attach_printable("Error parsing fee")?; - // fee - tools::dump_biguint(&self.fee, &mut to_return) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Dump))?; + index += fee_size + 1; - // transactions hashes - for hash in self.transactions.iter() { - to_return.extend(hash); + if (data.len() - index) % 32 != 0 { + return Err( + Report::new(BlockError::TransactionBlock(TxBlockErrorKind::Parse)) + .attach_printable("transactions % 32 != 0"), + ); } - Ok(to_return) - } - - pub fn parse(data: &[u8]) -> Result { - let mut offset: usize = 0; - - // merkle tree root - let merkle_tree_root: [u8; 32] = data[..32].try_into().unwrap(); - offset += 32; // inc offset + let mut transactions = Vec::::with_capacity((data.len() - index) / 32); - // default info - let default_info = BasicInfo::parse(&data[offset..]) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?; - - offset += default_info.get_dump_size(); // inc offset - - // fee - let (fee, _offset) = tools::load_biguint(&data[offset..]) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?; - - offset += _offset; // inc offset - - if (data.len() - offset) % 32 != 0 { - return Err(BlockError::TransactionBlock(TxBlockErrorKind::Parse).into()); + while index < data.len() { + transactions.push(unsafe { data[index..index + 32].try_into().unwrap_unchecked() }); + index += 32; } - // parse transaction hashes - let transactions: Vec<[u8; 32]> = data[offset..] - .chunks_exact(32) - .map(|hash| unsafe { hash.try_into().unwrap_unchecked() }) - .collect(); - - Ok(TransactionBlock { - transactions: Arc::new(transactions), + Ok(Self { fee, - merkle_tree: None, merkle_tree_root, default_info, + transactions, }) } - pub fn parse_with_transactions( - data: &[u8], - block_size: u32, - ) -> Result<(TransactionBlock, Vec>), BlockError> { - let mut offset: usize = 0; + pub fn hash(&self) -> Result { + let dump: Vec = self.dump()?; - // merkle tree root - let merkle_tree_root: [u8; 32] = data[..32].try_into().unwrap(); - offset += 32; // inc offset + Ok(tools::hash(&dump)) + } +} - // default info - let default_info = BasicInfo::parse(&data[offset..]) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?; +#[derive(Debug)] +pub struct DerivativeBlock { + pub default_info: BasicInfo, + //pub payment_transaction: Hash, +} - offset += default_info.get_dump_size(); // inc offset +pub trait Block { + fn hash(&self) -> Result; + fn get_dump_size(&self) -> usize; + fn dump(&self) -> Result, BlockError>; + fn get_info(&self) -> &BasicInfo; + fn get_merkle_root(&self) -> Hash; + fn verify_block(&self, prev_hash: &Hash) -> bool; + fn get_founder(&self) -> &Address; + fn get_fee(&self) -> U256; + fn get_type(&self) -> Headers; + fn validate(&self, prev_block: Option) -> Result; +} - // fee - let (fee, _offset) = tools::load_biguint(&data[offset..]) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?; +impl Block for DerivativeBlock { + fn get_dump_size(&self) -> usize { + self.default_info.get_dump_size() + 1 + } + fn get_info(&self) -> &BasicInfo { + &self.default_info + } + fn get_type(&self) -> Headers { + Headers::DerivativeBlock + } + fn dump(&self) -> Result, BlockError> { + let size = self.get_dump_size(); + let mut to_return = Vec::::with_capacity(size); - offset += _offset; // inc offset + to_return.push(Headers::DerivativeBlock as u8); + self.default_info.dump(&mut to_return)?; - // transactions - let amount_of_transactions: u16 = - u16::from_be_bytes(data[offset..offset + 2].try_into().unwrap()); - offset += 2; // inc offset - - let mut transactions: Vec> = - Vec::with_capacity(amount_of_transactions as usize); - - for _ in 0..amount_of_transactions { - let transaction_size: u32 = - u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap()) - 1; - - offset += 4; // inc offset - - let header = Headers::from_u8(data[offset]) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?; - offset += 1; - - //let mut trtk: TransactionToken = TransactionToken::new(None, None); - let tr = match header { - Headers::Transaction => Transaction::parse( - &data[offset..offset + (transaction_size as usize)], - transaction_size as u64, - ) - .change_context(BlockError::TransactionBlock(TxBlockErrorKind::Parse))?, - Headers::Token => { - return Err(Report::new(BlockError::NotImplemented( - NotImplementedKind::Token, - ))); - } - _ => { - return Err(Report::new(BlockError::HeaderError( - DumpHeadersErrorKind::WrongHeader, - ))); - } - }; - - offset += transaction_size as usize; // inc offset - - transactions.push(Box::new(tr)); + Ok(to_return) + } + fn hash(&self) -> Result { + Ok(tools::hash(&self.dump()?)) + } + fn get_merkle_root(&self) -> Hash { + unimplemented!() + //self.payment_transaction + } + fn verify_block(&self, prev_hash: &Hash) -> bool { + self.default_info.previous_hash.eq(prev_hash) + } + fn get_founder(&self) -> &Address { + &self.default_info.founder + } + fn get_fee(&self) -> U256 { + U256::zero() + } + fn validate(&self, prev_block: Option) -> Result { + if prev_block.is_none() { + return Ok(true); + } + let prev_block = unsafe { prev_block.unwrap_unchecked() }; + if !self.default_info.previous_hash.eq(&prev_block + .hash() + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Hash)) + .attach_printable(format!( + "Error hashing block with height {}", + prev_block.get_info().height + ))?) + { + return Ok(false); } - if offset != block_size as usize { - return Err(Report::new(BlockError::TransactionBlock( - TxBlockErrorKind::Parse, - ))); + let mut prev_difficulty = prev_block.get_info().difficulty; + recalculate_difficulty( + prev_block.get_info().timestamp, + self.default_info.timestamp, + &mut prev_difficulty, + ); + + if self.default_info.difficulty != prev_difficulty { + return Ok(false); } - let transactions_hashes: Vec<[u8; 32]> = transactions.iter().map(|tr| tr.hash()).collect(); + if !check_pow( + &self.get_merkle_root(), + &prev_block.get_info().difficulty, + &self.default_info.pow, + ) { + return Ok(false); + } - Ok(( - TransactionBlock::new(transactions_hashes, fee, default_info, merkle_tree_root), - transactions, - )) + Ok(true) } +} - pub fn hash(&self) -> Result<[u8; 32], BlockError> { - let dump: Vec = self.dump()?; - - Ok(tools::hash(&dump)) +impl DerivativeBlock { + pub fn parse(data: &[u8]) -> Result { + if data.len() < 32 { + return Err( + Report::new(BlockError::DerivativeBlock(DerivativeBlockErrorKind::Parse)) + .attach_printable("data.len() < 32"), + ); + } + let default_info: BasicInfo = BasicInfo::parse(data)?; + Ok(DerivativeBlock { + default_info, + //payment_transaction, + }) } } -impl MainChainBlock for TransactionBlock { - fn hash(&self) -> Result<[u8; 32], BlockError> { +impl Block for TransactionBlock { + fn hash(&self) -> Result { self.hash() } - fn get_dump_size(&self) -> usize { self.get_dump_size() } - fn dump(&self) -> Result, BlockError> { self.dump() } - fn get_info(&self) -> BasicInfo { - self.default_info.clone() + fn get_info(&self) -> &BasicInfo { + &self.default_info } - fn get_merkle_root(&self) -> [u8; 32] { + fn get_merkle_root(&self) -> Hash { self.merkle_tree_root } - - fn verify_block(&self, prev_hash: &[u8; 32]) -> bool { + fn verify_block(&self, prev_hash: &Hash) -> bool { self.default_info.previous_hash.eq(prev_hash) } - - fn get_transactions(&self) -> Arc> { - self.transactions.clone() - } - - fn get_founder(&self) -> &[u8; 33] { + fn get_founder(&self) -> &Address { &self.default_info.founder } - - fn get_fee(&self) -> BigUint { - self.fee.clone() + fn get_fee(&self) -> U256 { + self.fee } -} -pub struct TokenBlock { - pub default_info: BasicInfo, - pub token_signature: String, - pub payment_transaction: Transaction, -} - -impl TokenBlock { - pub fn new( - default_info: BasicInfo, - token_signature: String, - payment_transaction: Transaction, - ) -> TokenBlock { - TokenBlock { - default_info, - token_signature, - payment_transaction, - } + fn get_type(&self) -> Headers { + Headers::TransactionBlock } - pub fn get_dump_size(&self) -> usize { - self.default_info.get_dump_size() - + self.token_signature.len() - + 1 - + self.payment_transaction.get_dump_size() - } - - pub fn dump(&self) -> Result, BlockError> { - let dump_size: usize = self.get_dump_size(); - - let mut dump: Vec = Vec::with_capacity(dump_size); - - // header - dump.push(Headers::TokenBlock as u8); - - // // dumping token signature - // for byte in self.token_signature.as_bytes().iter(){ - // dump.push(*byte); - // } - // dump.push(0); - - // dumping payment transaction - let transaction_len: u32 = self.payment_transaction.get_dump_size() as u32; - dump.extend(transaction_len.to_be_bytes().iter()); - - let result = self - .payment_transaction - .dump() - .change_context(BlockError::TokenBlock(TokenBlockErrorKind::Dump))?; - - dump.extend(result); - - // dumping default info - self.default_info - .dump(&mut dump) - .change_context(BlockError::TokenBlock(TokenBlockErrorKind::Dump))?; - - Ok(dump) - } - - pub fn parse(data: &[u8], block_size: u32) -> Result { - let mut offset: usize = 0; - // parsing token signature - let token_signature: String = String::new(); - // for byte in data{ - // offset += 1; - // if *byte == 0{ - // break; - // } - // token_signature.push(*byte as char); - // } - - // parsing transaction - let transaction_size: u32 = - u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap()); - offset += 4; - - if data[offset] != Headers::Transaction as u8 { - return Err(Report::new(BlockError::TokenBlock( - TokenBlockErrorKind::Parse, - ))); + fn validate(&self, prev_block: Option) -> Result { + if prev_block.is_none() { + return Ok(true); + } + let prev_block = unsafe { prev_block.unwrap_unchecked() }; + if !self.default_info.previous_hash.eq(&prev_block + .hash() + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Hash)) + .attach_printable(format!( + "Error hashing block with height {}", + prev_block.get_info().height + ))?) + { + return Ok(false); } - offset += 1; - - let payment_transaction = Transaction::parse( - &data[offset..offset + transaction_size as usize], - (transaction_size - 1) as u64, - ) - .attach_printable("Error parsing token block: couldn't parse transaction") - .change_context(BlockError::TokenBlock(TokenBlockErrorKind::Parse))?; - - offset += (transaction_size - 1) as usize; - // parsing basic info - let default_info = BasicInfo::parse(&data[offset..block_size as usize]) - .attach_printable("Error parsing token block: couldn't parse basic info") - .change_context(BlockError::TokenBlock(TokenBlockErrorKind::Parse))?; + let merkle_tree = merkletree::MerkleTree::build_tree(&self.transactions); + if !self.merkle_tree_root.eq(merkle_tree.get_root()) { + return Ok(false); + } - offset += default_info.get_dump_size(); + let mut prev_difficulty = prev_block.get_info().difficulty; + recalculate_difficulty( + prev_block.get_info().timestamp, + self.default_info.timestamp, + &mut prev_difficulty, + ); - if offset != block_size as usize { - return Err(Report::new(BlockError::TokenBlock( - TokenBlockErrorKind::Parse, - ))); + if self.default_info.difficulty != prev_difficulty { + return Ok(false); } - Ok(TokenBlock { - default_info, - token_signature, - payment_transaction, - }) - } - - pub fn hash(&self) -> Result<[u8; 32], BlockError> { - let dump: Vec = self.dump().unwrap(); + if !check_pow( + &self.merkle_tree_root, + &prev_block.get_info().difficulty, + &self.default_info.pow, + ) { + return Ok(false); + } - Ok(tools::hash(&dump)) + Ok(true) } } +#[derive(Debug)] pub struct SummarizeBlock { - default_info: BasicInfo, - founder_transaction: [u8; 32], + pub default_info: BasicInfo, + pub merkle_tree_root: Hash, } impl SummarizeBlock { - pub fn new(default_info: BasicInfo, founder_transaction: [u8; 32]) -> SummarizeBlock { - SummarizeBlock { - default_info, - founder_transaction, - } - } - - pub fn get_dump_size(&self) -> usize { - 1 // header - +self.default_info.get_dump_size() - +32 - } - - pub fn dump(&self) -> Result, BlockError> { - let mut to_return: Vec = Vec::with_capacity(self.get_dump_size()); - - // header - to_return.push(Headers::SummarizeBlock as u8); - - // dump transaction - to_return.extend(self.founder_transaction); - - // dump basic info - self.default_info.dump(&mut to_return)?; - - Ok(to_return) - } - - pub fn parse(data: &[u8]) -> Result { + pub fn parse(data: &[u8]) -> Result { if data.len() <= 32 { return Err( Report::new(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Parse)) @@ -608,68 +432,124 @@ impl SummarizeBlock { ); } - // parse transaction - let founder_transaction: [u8; 32] = unsafe { data[0..32].try_into().unwrap_unchecked() }; + let mut index = 0; + + let merkle_tree_root: Hash = unsafe { data[0..32].try_into().unwrap_unchecked() }; + index += 32; - // parse default info - let default_info = BasicInfo::parse(&data[32..]) - .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Parse))?; + let default_info = BasicInfo::parse(&data[index..]) + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Parse)) + .attach_printable("Error parsing default data")?; - Ok(SummarizeBlock { + Ok(Self { default_info, - founder_transaction, + merkle_tree_root, }) } +} - pub fn hash(&self) -> Result<[u8; 32], BlockError> { - let result = self +impl Block for SummarizeBlock { + fn get_type(&self) -> Headers { + Headers::SummarizeBlock + } + fn hash(&self) -> Result { + let dump = self .dump() - .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Hash)); + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Hash))?; - let dump: Vec = unsafe { result.unwrap_unchecked() }; + //let dump: Vec = unsafe { result.unwrap_unchecked() }; Ok(tools::hash(&dump)) } -} - -impl MainChainBlock for SummarizeBlock { - fn hash(&self) -> Result<[u8; 32], BlockError> { - self.hash() - } fn get_dump_size(&self) -> usize { - self.get_dump_size() + 1 + 32 + self.default_info.get_dump_size() } fn dump(&self) -> Result, BlockError> { - self.dump() + let mut to_return: Vec = Vec::with_capacity(self.get_dump_size()); + + // header + to_return.push(Headers::SummarizeBlock as u8); + + // merkle tree + to_return.extend(self.merkle_tree_root.iter()); + + // default info + self.default_info + .dump(&mut to_return) + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Dump)) + .attach_printable("Error dumping default info")?; + + Ok(to_return) } - fn get_info(&self) -> BasicInfo { - self.default_info.clone() + + fn get_info(&self) -> &BasicInfo { + &self.default_info } - fn get_merkle_root(&self) -> [u8; 32] { - self.founder_transaction + + fn get_merkle_root(&self) -> Hash { + self.merkle_tree_root } - fn verify_block(&self, prev_hash: &[u8; 32]) -> bool { + fn verify_block(&self, prev_hash: &Hash) -> bool { self.default_info.previous_hash.eq(prev_hash) } - fn get_transactions(&self) -> Arc> { - Arc::new(vec![self.founder_transaction]) + fn get_founder(&self) -> &Address { + &self.default_info.founder } - fn get_founder(&self) -> &[u8; 33] { - &self.default_info.founder + fn get_fee(&self) -> U256 { + U256::zero() } - fn get_fee(&self) -> BigUint { - 0usize.into() + fn validate(&self, prev_block: Option) -> Result { + if prev_block.is_none() { + return Ok(true); + } + let prev_block = unsafe { prev_block.unwrap_unchecked() }; + if !self.default_info.previous_hash.eq(&prev_block + .hash() + .change_context(BlockError::SummarizeBlock(SummarizeBlockErrorKind::Hash)) + .attach_printable(format!( + "Error hashing block with height {}", + prev_block.get_info().height + ))?) + { + return Ok(false); + } + + // let merkle_tree = merkletree::MerkleTree::build_tree(&self.transactions); + // if !self.merkle_tree_root.eq(merkle_tree.get_root()) { + // return Ok(false); + // } + + let mut prev_difficulty = prev_block.get_info().difficulty; + recalculate_difficulty( + prev_block.get_info().timestamp, + self.default_info.timestamp, + &mut prev_difficulty, + ); + + if self.default_info.difficulty != prev_difficulty { + return Ok(false); + } + + if !check_pow( + &self.merkle_tree_root, + &prev_block.get_info().difficulty, + &self.default_info.pow, + ) { + return Ok(false); + } + + Ok(true) } } -/// Deserializes block's dump into MainChainBlockArc -pub fn deserialize_main_chain_block(dump: &[u8]) -> Result { +/// Deserializes block's dump into BlockArc +pub fn deserialize_main_chain_block(dump: &[u8]) -> Result { if dump.is_empty() { return Err( Report::new(BlockError::HeaderError(DumpHeadersErrorKind::WrongHeader)) @@ -680,7 +560,7 @@ pub fn deserialize_main_chain_block(dump: &[u8]) -> Result Arc::new(TransactionBlock::parse(&dump[1..])?), Headers::SummarizeBlock => Arc::new(SummarizeBlock::parse(&dump[1..])?), _ => { @@ -694,111 +574,23 @@ pub fn deserialize_main_chain_block(dump: &[u8]) -> Result Result<[u8; 32], BlockError> { - Ok(GENESIS_BLOCK) - } - - fn get_dump_size(&self) -> usize { - 0 - } - - fn dump(&self) -> Result, BlockError> { - Ok(vec![ - 5, 0, 0, 0, 0, 95, 62, 101, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3, 27, 132, 197, 86, 123, 18, - 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, 52, 96, 72, 25, 255, 156, 23, - 245, 233, 213, 221, 7, 143, 1, 0, - ]) - } - - fn get_info(&self) -> BasicInfo { - BasicInfo { - timestamp: INCEPTION_TIMESTAMP, - pow: vec![0], - previous_hash: [0; 32], - height: 0, - difficulty: BEGINNING_DIFFICULTY, - founder: ROOT_PUBLIC_ADDRESS, - } - } - - fn get_merkle_root(&self) -> [u8; 32] { - [0; 32] - } - - fn verify_block(&self, prev_hash: &[u8; 32]) -> bool { - [0; 32].eq(prev_hash) - } - - fn get_transactions(&self) -> Arc> { - Arc::new(Vec::with_capacity(0)) - } - - fn get_founder(&self) -> &[u8; 33] { - &ROOT_PUBLIC_ADDRESS - } - - fn get_fee(&self) -> BigUint { - 0usize.into() - } -} - -pub trait MainChainBlock { - fn hash(&self) -> Result<[u8; 32], BlockError>; - fn get_dump_size(&self) -> usize; - fn dump(&self) -> Result, BlockError>; - fn get_info(&self) -> BasicInfo; - fn get_merkle_root(&self) -> [u8; 32]; - fn verify_block(&self, prev_hash: &[u8; 32]) -> bool; - fn get_transactions(&self) -> Arc>; - fn get_founder(&self) -> &[u8; 33]; - fn get_fee(&self) -> BigUint; -} - -//pub type MainChainBlockBox = Box; -pub type MainChainBlockArc = Arc; - -// impl Eq for MainChainBlockBox {} - -// impl PartialEq for MainChainBlockBox { -// fn eq(&self, other: &Self) -> bool { -// self.get_info().timestamp == other.get_info().timestamp -// } -// } - -// impl PartialOrd for MainChainBlockBox { -// fn partial_cmp(&self, other: &Self) -> Option { -// Some(self.get_info().timestamp.cmp(&other.get_info().timestamp)) -// } -// } - -// impl Ord for MainChainBlockBox { -// fn cmp(&self, other: &Self) -> Ordering { -// self.get_info().timestamp.cmp(&other.get_info().timestamp) -// } -// } +pub type BlockArc = Arc; -impl Eq for dyn MainChainBlock + Send + Sync {} +impl Eq for dyn Block + Send + Sync {} -impl PartialEq for dyn MainChainBlock + Send + Sync { +impl PartialEq for dyn Block + Send + Sync { fn eq(&self, other: &Self) -> bool { self.get_info().timestamp == other.get_info().timestamp } } -impl PartialOrd for dyn MainChainBlock + Send + Sync { +impl PartialOrd for dyn Block + Send + Sync { fn partial_cmp(&self, other: &Self) -> Option { Some(self.get_info().timestamp.cmp(&other.get_info().timestamp)) } } -impl Ord for dyn MainChainBlock + Send + Sync { +impl Ord for dyn Block + Send + Sync { fn cmp(&self, other: &Self) -> Ordering { self.get_info().timestamp.cmp(&other.get_info().timestamp) } diff --git a/src/blockchaintree.rs b/src/blockchaintree.rs index 5c32191..1cfbb33 100644 --- a/src/blockchaintree.rs +++ b/src/blockchaintree.rs @@ -1,2564 +1,555 @@ -#![allow(non_snake_case)] -use crate::block::{ - self, BasicInfo, GenesisBlock, MainChainBlock, MainChainBlockArc, SummarizeBlock, TokenBlock, - TransactionBlock, +use std::{collections::HashMap, path::Path, sync::Arc}; + +use crate::{ + block::{self, Block as _, BlockArc}, + chain, + errors::{BCTreeErrorKind, BlockChainTreeError, ChainErrorKind}, + merkletree, + static_values::{ + self, AMMOUNT_SUMMARY, BLOCKS_PER_EPOCH, COINS_PER_CYCLE, GAS_SUMMARY, + OLD_AMMOUNT_SUMMARY, OLD_GAS_SUMMARY, ROOT_PUBLIC_ADDRESS, + }, + tools, + transaction::Transaction, + types::Hash, }; -use crate::merkletree::MerkleTree; -use crate::tools::{self, check_pow}; -use crate::transaction::{Transaction, Transactionable, TransactionableItem}; -use num_bigint::BigUint; -use std::cmp::Ordering; -use std::collections::binary_heap::Iter; -use std::collections::{BinaryHeap, HashMap, HashSet}; -use std::convert::TryInto; - -use crate::summary_db::SummaryDB; - -use crate::dump_headers::Headers; -use hex::ToHex; -use lazy_static::lazy_static; +use error_stack::{Report, ResultExt}; +use primitive_types::U256; use sled::Db; use std::fs; -use std::fs::File; -use std::io::Read; -use std::io::Write; -use std::path::Path; -use std::str::{self}; -use std::sync::Arc; -use tokio::sync::{RwLock, RwLockWriteGuard}; - -use crate::errors::*; -use error_stack::{IntoReport, Report, Result, ResultExt}; - -static BLOCKCHAIN_DIRECTORY: &str = "./BlockChainTree/"; - -static AMMOUNT_SUMMARY: &str = "./BlockChainTree/SUMMARY/"; -static OLD_AMMOUNT_SUMMARY: &str = "./BlockChainTree/SUMMARYOLD/"; - -static MAIN_CHAIN_DIRECTORY: &str = "./BlockChainTree/MAIN/"; - -static DERIVATIVE_CHAINS_DIRECTORY: &str = "./BlockChainTree/DERIVATIVES/"; -static CHAINS_FOLDER: &str = "CHAINS/"; -//static DERIVATIVE_DB_DIRECTORY: BlockChainTreeError = "./BlockChainTree/DERIVATIVE/DB/"; - -static BLOCKS_FOLDER: &str = "BLOCKS/"; -static REFERENCES_FOLDER: &str = "REF/"; -static TRANSACTIONS_FOLDER: &str = "TRANSACTIONS/"; -static CONFIG_FILE: &str = "Chain.config"; -static LOOKUP_TABLE_FILE: &str = "LookUpTable.dat"; -static TRANSACTIONS_POOL: &str = "TRXS_POOL.pool"; -pub static GENESIS_BLOCK: [u8; 32] = [ - 166, 82, 122, 252, 228, 62, 251, 177, 190, 166, 167, 44, 232, 163, 184, 96, 92, 49, 164, 95, - 98, 237, 220, 9, 75, 157, 169, 55, 251, 191, 211, 12, -]; -pub static BEGINNING_DIFFICULTY: [u8; 32] = [ - 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -]; -static MAX_DIFFICULTY: [u8; 32] = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128, -]; - -pub static ROOT_PRIVATE_ADDRESS: [u8; 32] = [1u8; 32]; -pub static ROOT_PUBLIC_ADDRESS: [u8; 33] = [ - 3, 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, 52, 96, - 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, -]; - -pub static INCEPTION_TIMESTAMP: u64 = 1597924800; - -lazy_static! { - // one coin is 100_000_000 smol coins - static ref COIN_FRACTIONS: BigUint = BigUint::from(100_000_000usize); - static ref INITIAL_FEE: BigUint = BigUint::from(16666666usize); // 100_000_000//4 - static ref FEE_STEP: BigUint = BigUint::from(392156usize); // 100_000_000//255 - static ref MAIN_CHAIN_PAYMENT: BigUint = INITIAL_FEE.clone(); - static ref COINS_PER_CYCLE:BigUint = (MAIN_CHAIN_PAYMENT.clone()*2000usize*BLOCKS_PER_ITERATION) + COIN_FRACTIONS.clone()*10000usize; +pub struct BlockChainTree { + main_chain: chain::MainChain, + derivative_chains: HashMap<[u8; 33], chain::DerivativeChain>, + summary_db: Db, + old_summary_db: Db, + gas_db: Db, + old_gas_db: Db, } -//static MAX_TRANSACTIONS_PER_BLOCK: usize = 3000; -static BLOCKS_PER_ITERATION: usize = 12960; +impl BlockChainTree { + pub fn new() -> Result> { + let path_summary = Path::new(AMMOUNT_SUMMARY); + let path_summary_old = Path::new(OLD_AMMOUNT_SUMMARY); + let path_gas = Path::new(GAS_SUMMARY); + let path_gas_old = Path::new(OLD_GAS_SUMMARY); + + // open summary DB + let summary_db = sled::open(path_summary) + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) + .attach_printable("failed to open summary db")?; -type TrxsPool = BinaryHeap; + // open old summary DB + let old_summary_db = sled::open(path_summary_old) + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) + .attach_printable("failed to open old summary db")?; -type DerivativesCell = Arc>; -type Derivatives = Arc>>; + // open gas DB + let gas_db = sled::open(path_gas) + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) + .attach_printable("failed to open gas db")?; -#[derive(Default)] -pub struct TransactionsPool { - pool: TrxsPool, - hashes: HashSet<[u8; 32]>, -} + let old_gas_db = sled::open(path_gas_old) + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) + .attach_printable("failed to open old gas db")?; + let main_chain = chain::MainChain::new()?; -impl TransactionsPool { - pub fn new() -> TransactionsPool { - TransactionsPool::default() - } - pub fn with_capacity(capacity: usize) -> TransactionsPool { - TransactionsPool { - pool: BinaryHeap::with_capacity(capacity), - hashes: HashSet::with_capacity(capacity), - } + if main_chain.get_height() == U256::one() { + summary_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let mut buf: Vec = + Vec::with_capacity(tools::u256_size(&COINS_PER_CYCLE)); + tools::dump_u256(&COINS_PER_CYCLE, &mut buf).unwrap(); + db.insert(&(ROOT_PUBLIC_ADDRESS) as &[u8], buf)?; + Ok(()) + }, + ) + .unwrap(); + } + Ok(Self { + main_chain, + derivative_chains: HashMap::new(), + summary_db, + old_summary_db, + gas_db, + old_gas_db, + }) } - pub fn push(&mut self, transaction: TransactionableItem) -> bool { - if !self.hashes.insert(transaction.hash()) { - return false; + pub fn get_derivative_chain( + &mut self, + owner: &[u8; 33], + ) -> Result> { + if let Some(chain) = self.derivative_chains.get(owner) { + return Ok(chain.clone()); } - self.pool.push(transaction); - true - } - - pub fn len(&self) -> usize { - self.hashes.len() - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn transactions_iter(&self) -> Iter<'_, TransactionableItem> { - self.pool.iter() - } - - pub fn pop(&mut self) -> Option<([u8; 32], TransactionableItem)> { - let tr = self.pool.pop()?; - let hash = tr.hash(); - self.hashes.remove(&hash); - Some((hash, tr)) - } - - pub fn transaction_exists(&self, hash: &[u8; 32]) -> bool { - self.hashes.contains(hash) + let last_block = self.main_chain.get_last_block()?.unwrap(); // practically cannot fail + let derivative_chain = + chain::DerivativeChain::new(&hex::encode(owner), &last_block.hash().unwrap())?; + self.derivative_chains + .insert(*owner, derivative_chain.clone()); + Ok(derivative_chain) } -} - -#[derive(Clone)] -pub struct Chain { - db: Db, - height_reference: Db, - transactions: Db, - height: Arc>, - genesis_hash: [u8; 32], - difficulty: Arc>, -} - -impl Chain { - /// Open chain with config - pub fn new() -> Result { - let root = String::from(MAIN_CHAIN_DIRECTORY); - let path_blocks_st = root.clone() + BLOCKS_FOLDER; - let path_references_st = root.clone() + REFERENCES_FOLDER; - let path_transactions_st = root.clone() + TRANSACTIONS_FOLDER; - let path_height_st = root + CONFIG_FILE; - - let path_blocks = Path::new(&path_blocks_st); - let path_reference = Path::new(&path_references_st); - let path_transactions = Path::new(&path_transactions_st); - let path_height = Path::new(&path_height_st); - - // open blocks DB - let db = sled::open(path_blocks) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to open blocks db")?; - - // open height references DB - let height_reference = sled::open(path_reference) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to open references db")?; - - // open transactions DB - let transactions_db = sled::open(path_transactions) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to open transactions db")?; - - let mut file = File::open(path_height) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init))?; - - // read height from config - let mut height_bytes: [u8; 8] = [0; 8]; - - file.read_exact(&mut height_bytes) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to read config")?; - - let height: u64 = u64::from_be_bytes(height_bytes); - - // read genesis hash - let mut genesis_hash: [u8; 32] = [0; 32]; - file.read_exact(&mut genesis_hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to read genesis hash")?; - // read difficulty - let mut difficulty: [u8; 32] = [0; 32]; - file.read_exact(&mut difficulty) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to read difficulty")?; - - Ok(Chain { - db, - height_reference, - transactions: transactions_db, - height: Arc::new(RwLock::new(height)), - genesis_hash, - difficulty: Arc::new(RwLock::new(difficulty)), - }) + pub fn get_main_chain(&self) -> chain::MainChain { + self.main_chain.clone() } - /// Remove heigh reference for supplied hash - async fn remove_height_reference(&self, hash: &[u8; 32]) -> Result<(), BlockChainTreeError> { - self.height_reference - .remove(hash) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToRemoveHeighReference, - )) - .attach_printable("Hash: {hash:?}")?; - - self.height_reference - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToRemoveHeighReference, - )) - .attach_printable("Hash: {hash:?}")?; + pub fn add_amount( + &self, + owner: &[u8], + amount: U256, + ) -> Result<(), Report> { + self.summary_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let prev_amount = match db.get(owner)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + let new_amount = prev_amount + amount; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&new_amount)); + tools::dump_u256(&new_amount, &mut buf).unwrap(); + db.insert(owner, buf)?; + Ok(()) + }, + ) + .unwrap(); Ok(()) } - /// Remove all transactions for supplied transactions hashes - /// - /// fee should be same for the supplied transactions - /// - /// Transactions should be rotated newer - older - /// - /// will update amounts in summary db - async fn remove_transactions<'a, I>( + pub fn set_amount( &self, - transactions: I, - fee: BigUint, - summary_db: &SummaryDB, - ) -> Result<(), BlockChainTreeError> - where - I: Iterator, - { - for transaction_hash in transactions { - let transaction_dump = self - .transactions - .remove(transaction_hash) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToRemoveTransaction, - )) - .attach_printable(format!("Hash: {transaction_hash:?}"))? - .ok_or(BlockChainTreeError::Chain( - ChainErrorKind::FailedToRemoveTransaction, - )) - .into_report() - .attach_printable(format!("Transaction with hash: {transaction_hash:?}"))?; - - // TODO: rewrite transaction parsing - let transaction = - Transaction::parse(&transaction_dump[1..], (transaction_dump.len() - 1) as u64) - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToRemoveTransaction, - )) - .attach_printable(format!( - "Error parsing transaction with hash: {transaction_hash:?}" - ))?; + owner: &[u8], + amount: U256, + ) -> Result<(), Report> { + self.summary_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&amount)); + tools::dump_u256(&amount, &mut buf).unwrap(); + db.insert(owner, buf)?; + Ok(()) + }, + ) + .unwrap(); - summary_db - .add_funds(transaction.get_sender(), transaction.get_amount()) - .await?; - summary_db - .decrease_funds( - transaction.get_receiver(), - &(transaction.get_amount() - &fee), - ) - .await?; - } Ok(()) } - /// Removes blocks references and associated transactions - /// - /// end_height > start_height - /// - /// removes all blocks from start_height to end_height - /// - /// utilizes remove_height_reference() and remove_transactions() - pub async fn remove_blocks( + pub fn sub_amount( &self, - start_height: u64, - end_height: u64, - summary_db: &SummaryDB, - ) -> Result<(), BlockChainTreeError> { - for height in end_height - 1..start_height { - let block = self.find_by_height(height).await?.unwrap(); // fatal error - - let hash = block.hash().change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - self.remove_height_reference(&hash).await.unwrap(); // fatal error - - self.remove_transactions( - block.get_transactions().iter().rev(), - block.get_fee(), - summary_db, + owner: &[u8], + amount: U256, + ) -> Result<(), Report> { + self.summary_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let prev_amount = match db.get(owner)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + if prev_amount < amount { + return Err(sled::transaction::ConflictableTransactionError::Abort(())); + } + let new_amount = prev_amount + amount; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&new_amount)); + tools::dump_u256(&new_amount, &mut buf).unwrap(); + db.insert(owner, buf)?; + Ok(()) + }, ) - .await - .unwrap(); // fatal error - } + .unwrap(); + Ok(()) } + pub fn get_amount(&self, owner: &[u8; 33]) -> Result> { + match self + .summary_db + .get(owner) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::GetFunds, + )) + .attach_printable("failed to get funds")? + { + Some(v) => Ok(tools::load_u256(&v).unwrap().0), + None => Ok(U256::zero()), + } + } - /// Overwrite block with same height - /// - /// Adds a block to db under it's height - /// - /// Removes higher blocks references and removes associated transactions - /// - /// uses remove_blocks() to remove higher blocks and transactions - /// - /// sets current height to the block's height + 1 - /// - /// Doesn't change difficulty - pub async fn block_overwrite( + pub fn send_amount( &self, - block: &MainChainBlockArc, - summary_db: &SummaryDB, - ) -> Result<(), BlockChainTreeError> { - let mut height = self.height.write().await; - - let dump = block - .dump() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - let hash = tools::hash(&dump); - - let height_block = block.get_info().height; - let height_bytes = height.to_be_bytes(); - - self.remove_blocks(height_block, *height, summary_db) - .await?; - - self.db - .insert(height_bytes, dump) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - self.height_reference - .insert(hash, &height_bytes) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + from: &[u8], + to: &[u8], + amount: U256, + ) -> Result<(), Report> { + self.summary_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let mut from_amount = match db.get(from)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + let mut to_amount = match db.get(to)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + if from_amount < amount { + return Err(sled::transaction::ConflictableTransactionError::Abort(())); + } - *height = height_block + 1; + from_amount -= amount; + to_amount += amount; - self.db - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&from_amount)); + tools::dump_u256(&from_amount, &mut buf).unwrap(); + db.insert(from, buf)?; - self.height_reference - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&to_amount)); + tools::dump_u256(&to_amount, &mut buf).unwrap(); + db.insert(to, buf)?; + Ok(()) + }, + ) + .unwrap(); Ok(()) } - /// Adds new block to the chain db, raw API function - /// - /// Adds block and sets heigh reference for it - /// - /// Doesn't check for blocks validity, just adds it directly to the end of the chain - pub async fn add_block_raw( - &self, - block: &impl MainChainBlock, - ) -> Result<(), BlockChainTreeError> { - let dump = block - .dump() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - let hash = tools::hash(&dump); - - let mut height = self.height.write().await; - let height_bytes = height.to_be_bytes(); - - self.db - .insert(height_bytes, dump) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - self.height_reference - .insert(hash, &height_bytes) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - *height += 1; - - //drop(height); - - self.db - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - self.height_reference - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + pub fn add_gas(&self, owner: &[u8], amount: U256) -> Result<(), Report> { + self.gas_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let prev_amount = match db.get(owner)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + let new_amount = prev_amount + amount; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&new_amount)); + tools::dump_u256(&new_amount, &mut buf).unwrap(); + db.insert(owner, buf)?; + Ok(()) + }, + ) + .unwrap(); Ok(()) } - - /// Add new transaction to the chain, raw API function - /// - /// Adds transaction into db of transactions, transaction should be also registered in the block - /// - /// Doesn't validate transaction - pub async fn add_transaction_raw( - &self, - transaction: &impl Transactionable, - ) -> Result<(), BlockChainTreeError> { - self.transactions - .insert( - transaction.hash(), - transaction - .dump() - .map_err(|e| { - e.change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - )) - }) - .attach_printable("failed to dump transaction")?, + pub fn sub_gas(&self, owner: &[u8], amount: U256) -> Result<(), Report> { + self.gas_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let prev_amount = match db.get(owner)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + if prev_amount < amount { + return Err(sled::transaction::ConflictableTransactionError::Abort(())); + } + let new_amount = prev_amount + amount; + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&new_amount)); + tools::dump_u256(&new_amount, &mut buf).unwrap(); + db.insert(owner, buf)?; + Ok(()) + }, ) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - )) - .attach_printable("failed to add transaction to database")?; - - self.transactions - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - ))?; + .unwrap(); Ok(()) } - - /// Add a batch of transactions - pub async fn add_transactions_raw( - &self, - transactions: Vec>, - ) -> Result<(), BlockChainTreeError> { - let mut batch = sled::Batch::default(); - for transaction in transactions { - batch.insert( - &transaction.hash(), - transaction - .dump() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - ))?, - ); + pub fn get_gas(&self, owner: &[u8; 33]) -> Result> { + match self + .gas_db + .get(owner) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::GetFunds, + )) + .attach_printable("failed to get gas amount")? + { + Some(v) => Ok(tools::load_u256(&v).unwrap().0), + None => Ok(U256::zero()), } - - self.transactions - .apply_batch(batch) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - ))?; - - self.transactions - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::AddingTransaction, - ))?; - - Ok(()) } - /// Get deserialized transaction by it's hash - pub async fn find_transaction( + pub fn send_gas( &self, - hash: &[u8; 32], - ) -> Result, BlockChainTreeError> { - let dump = if let Some(dump) = self - .transactions - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindTransaction)) - .attach_printable("Error getting transaction from database")? - .take() - { - dump - } else { - return Ok(None); - }; + from: &[u8], + to: &[u8], + amount: U256, + ) -> Result<(), Report> { + self.gas_db + .transaction( + |db| -> Result<(), sled::transaction::ConflictableTransactionError<()>> { + let mut from_amount = match db.get(from)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + let mut to_amount = match db.get(to)? { + Some(v) => tools::load_u256(&v).unwrap().0, + None => U256::zero(), + }; + if from_amount < amount { + return Err(sled::transaction::ConflictableTransactionError::Abort(())); + } + + from_amount -= amount; + to_amount += amount; - let transaction = if dump[0] == Headers::Transaction as u8 { - Transaction::parse(&dump[1..], (dump.len() - 1) as u64) - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindTransaction)) - .attach_printable("Error parsing transaction") - } else { - Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FindTransaction)) - .attach_printable("Unknown header"), + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&from_amount)); + tools::dump_u256(&from_amount, &mut buf).unwrap(); + db.insert(from, buf)?; + + let mut buf: Vec = Vec::with_capacity(tools::u256_size(&to_amount)); + tools::dump_u256(&to_amount, &mut buf).unwrap(); + db.insert(to, buf)?; + Ok(()) + }, ) - }?; + .unwrap(); - Ok(Some(transaction)) + Ok(()) } - /// Get deserialized transaction by it's hash - pub async fn find_transaction_raw( + pub fn add_new_block( &self, - hash: &[u8; 32], - ) -> Result>, BlockChainTreeError> { - Ok(self - .transactions - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindTransaction)) - .attach_printable("Error getting transaction from database")? - .map(|dump| dump.to_vec())) - } + block: BlockArc, + transactions: &[Transaction], + ) -> Result<(), Report> { + self.main_chain.add_block(block)?; - /// Check whether transaction exists in the chain - pub fn transaction_exists(&self, hash: &[u8; 32]) -> Result { - Ok(self - .transactions - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindTransaction)) - .attach_printable("Error getting transaction from database")? - .is_some()) + self.main_chain.add_transactions(transactions) } - /// Get current chain's height - pub async fn get_height(&self) -> u64 { - *self.height.read().await - } + fn summarize(&self) -> Result<[u8; 32], Report> { + let mut hashes: Vec<[u8; 32]> = Vec::with_capacity(self.summary_db.len()); + for res in self.summary_db.iter() { + let (address, amount) = res + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::GetFunds, + )) + .attach_printable("failed to get funds from summary_db")?; + let gas_amount = self + .gas_db + .get(&address) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::GetFunds, + )) + .attach_printable("failed to get funds from summary_db")? + .map(|val| val.to_vec()) + .unwrap_or(Vec::with_capacity(0)); + let mut data_to_hash: Vec = + Vec::with_capacity(address.len() + amount.len() + gas_amount.len() + 2); + data_to_hash.extend(address.iter()); + data_to_hash.push(b'|'); + data_to_hash.extend(amount.iter()); + data_to_hash.push(b'|'); + data_to_hash.extend(gas_amount.iter()); - pub async fn get_locked_height(&self) -> RwLockWriteGuard { - self.height.write().await - } + hashes.push(tools::hash(&data_to_hash)); + } - /// Get current chain's difficulty - pub async fn get_difficulty(&self) -> [u8; 32] { - *self.difficulty.read().await - } + let merkle_tree = merkletree::MerkleTree::build_tree(&hashes); - pub async fn get_locked_difficulty(&self) -> RwLockWriteGuard<[u8; 32]> { - self.difficulty.write().await + Ok(*merkle_tree.get_root()) } - /// Get serialized block by it's height - pub async fn find_raw_by_height( - &self, - height: u64, - ) -> Result>, BlockChainTreeError> { - if height == 0 { - return Ok(Some(GenesisBlock {}.dump().change_context( - BlockChainTreeError::Chain(ChainErrorKind::FailedToHashBlock), - )?)); - } - let chain_height = self.height.read().await; - if height > *chain_height { - return Ok(None); - } - drop(chain_height); - let mut dump = self - .db - .get(height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))?; - - if let Some(dump) = dump.take() { - return Ok(Some(dump.to_vec())); - } - Ok(None) - } + pub async fn emmit_new_derivative_block( + &mut self, + pow: &[u8; 32], + founder: &[u8; 33], + timestamp: u64, + ) -> Result> { + let derivative_chain = self.get_derivative_chain(founder)?; + let (prev_hash, mut difficulty, prev_timestamp, height) = + if let Some(block) = derivative_chain.get_last_block()? { + ( + block.hash().unwrap(), + block.get_info().difficulty, + block.get_info().timestamp, + block.get_info().height, + ) + } else { + let block = self + .main_chain + .find_by_hash(&derivative_chain.genesis_hash)? + .ok_or(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; + ( + block.hash().unwrap(), + static_values::BEGINNING_DIFFICULTY, + block.get_info().timestamp, + U256::zero(), + ) + }; - /// Get serialized block by it's hash - pub async fn find_raw_by_hash( - &self, - hash: &[u8; 32], - ) -> Result>, BlockChainTreeError> { - let height = match self - .height_reference - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))? - { - None => { - return Ok(None); - } - Some(h) => { - u64::from_be_bytes(h.iter().copied().collect::>().try_into().unwrap()) - } + if !tools::check_pow(&prev_hash, &difficulty, pow) { + return Err(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::WrongPow).into()); + }; + tools::recalculate_difficulty(prev_timestamp, timestamp, &mut difficulty); + let default_info = block::BasicInfo { + timestamp, + pow: *pow, + previous_hash: prev_hash, + height: height + 1, + difficulty, + founder: *founder, }; - let block = self - .find_raw_by_height(height) - .await - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; - - Ok(block) + let block = block::DerivativeBlock { default_info }; + derivative_chain.add_block(&block)?; + Ok(Arc::new(block)) } - /// Get deserialized block by height - pub async fn find_by_height( - &self, - height: u64, - ) -> Result>, BlockChainTreeError> { - if height == 0 { - return Ok(Some(Arc::new(GenesisBlock {}))); - } - let chain_height = self.height.read().await; - if height > *chain_height { - return Ok(None); - } - drop(chain_height); - let dump = self - .db - .get(height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))?; - - if dump.is_none() { - return Ok(None); - } - - let dump = dump.unwrap(); - - Ok(Some( - block::deserialize_main_chain_block(&dump) - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))?, - )) - } + pub async fn emmit_new_main_block( + &mut self, + pow: &[u8; 32], + founder: &[u8; 33], + transactions: &[Hash], + timestamp: u64, + ) -> Result> { + let last_block = self.main_chain.get_last_block()?.unwrap(); // practically cannot fail + let prev_hash = last_block + .hash() + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DumpDb)) + .attach_printable("failed to hash block")?; - /// Get deserialized block by it's hash - pub async fn find_by_hash( - &self, - hash: &[u8; 32], - ) -> Result>, BlockChainTreeError> { - let height = match self - .height_reference - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))? - { - None => { - return Ok(None); - } - Some(h) => { - u64::from_be_bytes(h.iter().copied().collect::>().try_into().unwrap()) - } + let mut difficulty = last_block.get_info().difficulty; + if !tools::check_pow(&prev_hash, &difficulty, pow) { + return Err(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::WrongPow).into()); }; + tools::recalculate_difficulty(last_block.get_info().timestamp, timestamp, &mut difficulty); + let fee = tools::recalculate_fee(&difficulty); + let default_info = block::BasicInfo { + timestamp, + pow: *pow, + previous_hash: prev_hash, + height: last_block.get_info().height + 1, + difficulty, + founder: *founder, + }; + let new_block: block::BlockArc = + if ((last_block.get_info().height + 1) % BLOCKS_PER_EPOCH).is_zero() { + if !transactions.is_empty() { + return Err(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::SummarizeBlockWrongTransactionsAmount, + ) + .into()); + } - let block = self - .find_by_height(height) - .await - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; - - Ok(block) - } - - /// Dump config - /// - /// Dumps chain's config - pub async fn dump_config(&self) -> Result<(), BlockChainTreeError> { - let root = String::from(MAIN_CHAIN_DIRECTORY); - let path_config = root + CONFIG_FILE; + let merkle_tree_root = self.summarize()?; - let mut file = File::create(path_config) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig))?; + let summarize_block = Arc::new(block::SummarizeBlock { + default_info, + merkle_tree_root, + }); + self.rotate_dbs().await?; - file.write_all(&self.height.read().await.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to write height")?; + self.set_amount(&ROOT_PUBLIC_ADDRESS as &[u8], *COINS_PER_CYCLE)?; - file.write_all(&self.genesis_hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to write genesis block")?; + summarize_block + } else { + if transactions.is_empty() { + return Err(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::CreateMainChainBlock, + ) + .into()); + } - file.write_all(self.difficulty.read().await.as_ref()) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failes to write difficulty")?; + let merkle_tree = merkletree::MerkleTree::build_tree(transactions); + let transaction_block = Arc::new(block::TransactionBlock::new( + fee, + default_info, + *merkle_tree.get_root(), + Vec::from_iter(transactions.iter().cloned()), + )); + transaction_block + }; - Ok(()) + self.main_chain.add_block(new_block.clone())?; + Ok(new_block) } - /// Flushes all DBs and config - pub async fn flush(&self) -> Result<(), BlockChainTreeError> { - self.dump_config().await?; - - self.db + pub async fn flush(&self) -> Result<(), Report> { + self.main_chain.flush().await?; + self.summary_db .flush_async() .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to flush db")?; + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DumpDb)) + .attach_printable("failed to flush summary db")?; - self.height_reference + self.old_summary_db .flush_async() .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to flush height references")?; + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DumpDb)) + .attach_printable("failed to flush old summary db")?; - self.transactions + self.gas_db .flush_async() .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to flush transactions")?; - - Ok(()) - } - - /// Create new chain - /// - /// Creates new chain without config, creates necessary folders - pub fn new_without_config( - root_path: &str, - genesis_hash: &[u8; 32], - ) -> Result { - let root = String::from(root_path); - let path_blocks_st = root.clone() + BLOCKS_FOLDER; - let path_references_st = root.clone() + REFERENCES_FOLDER; - let path_transactions_st = root + TRANSACTIONS_FOLDER; - - let path_blocks = Path::new(&path_blocks_st); - let path_reference = Path::new(&path_references_st); - let path_transactions = Path::new(&path_transactions_st); - - // open blocks DB - let db = sled::open(path_blocks) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open blocks db")?; - - // open height references DB - let height_reference = sled::open(path_reference) - .into_report() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open references db")?; - - // open transactions DB - let transactions_db = sled::open(path_transactions) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) - .attach_printable("failed to open transactions db")?; - - Ok(Chain { - db, - height_reference, - transactions: transactions_db, - height: Arc::new(RwLock::new(1)), - genesis_hash: *genesis_hash, - difficulty: Arc::new(RwLock::new(BEGINNING_DIFFICULTY)), - }) - } - - /// Get serialized last block if the chain - pub async fn get_last_raw_block(&self) -> Result>, BlockChainTreeError> { - let height = self.height.read().await; - let last_block_index = *height - 1; - drop(height); - - self.find_raw_by_height(last_block_index).await - } - - /// Get deserialized last block of the chain - pub async fn get_last_block( - &self, - ) -> Result>, BlockChainTreeError> { - let height = self.height.read().await; - let last_block_index = *height - 1; - drop(height); - - self.find_by_height(last_block_index).await - } - - /// Get hash of the last block in chain - /// - /// Gets hash from the last record in height reference db - pub async fn get_last_hash(&self) -> Result<[u8; 32], BlockChainTreeError> { - if self.get_height().await == 0 { - return Ok(GENESIS_BLOCK); - } - Ok(self - .height_reference - .last() - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))? - .map(|(hash, _)| { - let mut hash_arr = [0u8; 32]; - hash.iter() - .zip(hash_arr.iter_mut()) - .for_each(|(val, cell)| *cell = *val); - hash_arr - }) - .unwrap_or(GENESIS_BLOCK)) - } - - /// Checks if the supplied pow is correct - /// - /// Takes hash of the last block for current time and checks against it - /// - /// Since this function checks data only in current time, it should not be used alone when adding new block, - /// - /// because of the way this implementation built it should be used with additional thread safety, such as locking `height` to ensure, - /// - /// that this function will get latest info - /// - /// P.S. it was made into separate function only because of mudularity and to provide raw API(later) - async fn check_pow_validity(&self, pow: &[u8]) -> Result { - let last_hash = self.get_last_hash().await?; - - let difficulty = self.get_difficulty().await; - Ok(tools::check_pow(&last_hash, &difficulty, pow)) - } - - /// Calculate fee for the difficulty - /// - /// takes difficulty and calculates fee for it - /// - /// TODO: Change the way fee calculated - pub fn calculate_fee(difficulty: &[u8; 32]) -> BigUint { - let mut leading_zeroes = 0; - for byte in difficulty { - let bytes_leading_zeroes = byte.count_zeros() as usize; - leading_zeroes += bytes_leading_zeroes; - if bytes_leading_zeroes < 8 { - break; - } - } - - INITIAL_FEE.clone() + (FEE_STEP.clone() * (leading_zeroes - 1)) - } - - /// Goes trough all the blocks in main chain and verifies each of them - pub async fn verify_chain(&self) -> Result<(), BlockChainTreeError> { - let height = *self.height.read().await; - - let prev_hash = self.genesis_hash; - for i in 0..height { - let block = match self.find_by_height(i).await? { - None => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FindByHeight, - )) - .attach_printable(format!("Block height: {i:?}"))) - } - Some(block) => block, - }; + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DumpDb)) + .attach_printable("failed to flush old summary db")?; - if !block.verify_block(&prev_hash) { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable(format!( - "Block hash: {:?}", - block.hash().change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - ))? - ))); - } - } + self.old_gas_db + .flush_async() + .await + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DumpDb)) + .attach_printable("failed to flush old summary db")?; Ok(()) } - pub fn block_exists(&self, hash: &[u8; 32]) -> Result { - self.height_reference - .contains_key(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE)) - } -} - -pub struct DerivativeChain { - db: Db, - height_reference: Db, - height: u64, - global_height: u64, - genesis_hash: [u8; 32], - difficulty: [u8; 32], -} - -impl DerivativeChain { - /// Open chain with config - pub fn new(root_path: &str) -> Result { - let root = String::from(root_path); - let path_blocks_st = root.clone() + BLOCKS_FOLDER; - let path_references_st = root.clone() + REFERENCES_FOLDER; - let path_height_st = root + CONFIG_FILE; + async fn rotate_dbs(&mut self) -> Result<(), Report> { + self.flush().await?; - let path_blocks = Path::new(&path_blocks_st); - let path_reference = Path::new(&path_references_st); - let path_height = Path::new(&path_height_st); - - // open blocks DB - let db = sled::open(path_blocks) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, - )) - .attach_printable("failed to open blocks db")?; + let path_summary = Path::new(AMMOUNT_SUMMARY); + let path_summary_old = Path::new(OLD_AMMOUNT_SUMMARY); + let path_gas = Path::new(GAS_SUMMARY); + let path_gas_old = Path::new(OLD_GAS_SUMMARY); - // open height references DB - let height_reference = sled::open(path_reference) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + fs::remove_dir_all(path_summary_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to open references db")?; + .attach_printable("failed to remove previous summary database")?; - let mut file = File::open(path_height) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + fs::create_dir(path_summary_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to open config")?; + .attach_printable("failed to create previous summary database folder")?; - // read height from config - let mut height_bytes: [u8; 8] = [0; 8]; - file.read_exact(&mut height_bytes) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + fs::remove_dir_all(path_gas_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to read config")?; + .attach_printable("failed to remove previous gas database")?; - let height: u64 = u64::from_be_bytes(height_bytes); - - // read genesis hash - let mut genesis_hash: [u8; 32] = [0; 32]; - file.read_exact(&mut genesis_hash) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + fs::create_dir(path_gas_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to open genesis hash from config")?; + .attach_printable("failed to remove previous gas database folder")?; - // read difficulty - let mut difficulty: [u8; 32] = [0; 32]; - file.read_exact(&mut difficulty) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + tools::copy_dir_all(path_summary, path_summary_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to read difficulty from config")?; + .attach_printable("failed to copy summary database")?; - // read global height - let mut global_height: [u8; 8] = [0; 8]; - file.read_exact(&mut global_height) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, + tools::copy_dir_all(path_gas, path_gas_old) + .change_context(BlockChainTreeError::BlockChainTree( + BCTreeErrorKind::MoveSummaryDB, )) - .attach_printable("failed to read global height from config")?; - - let global_height: u64 = u64::from_be_bytes(global_height); - - Ok(DerivativeChain { - db, - height_reference, - height, - genesis_hash, - difficulty, - global_height, - }) - } + .attach_printable("failed to copy gas database")?; - /// Adds block to the chain, sets heigh reference - pub async fn add_block(&mut self, block: &TokenBlock) -> Result<(), BlockChainTreeError> { - let dump = block - .dump() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::AddingBlock, - ))?; + self.old_summary_db = sled::open(path_summary_old) + .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) + .attach_printable("failed to open old summary db")?; - let hash = tools::hash(&dump); - - self.db - .insert(self.height.to_be_bytes(), dump) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, - )) - .attach_printable("failed to add block to db")?; - - self.height_reference - .insert(hash, &self.height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::Init, - )) - .attach_printable("failed to add reference to db")?; - - self.height += 1; - - self.db - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - self.height_reference - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; - - Ok(()) - } - - /// Get current height of the chain - pub fn get_height(&self) -> u64 { - self.height - } - - /// Get current difficulty of the chain - pub fn get_difficulty(&self) -> [u8; 32] { - self.difficulty - } - - /// Get global height of the chain - pub fn get_global_height(&self) -> u64 { - self.global_height - } - - /// Get deserialized block by it's height - pub fn find_by_height(&self, height: u64) -> Result, BlockChainTreeError> { - if height > self.height { - return Ok(None); - } - let dump = self - .db - .get(height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::FindByHeight, - )) - .attach_printable("failed to read block")?; - - if dump.is_none() { - return Ok(None); - } - let dump = dump.unwrap(); - - if dump[0] != Headers::TokenBlock as u8 { - return Err(Report::new(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::FindByHeight, - )) - .attach_printable("wrong header")); - } - let block = TokenBlock::parse(&dump[1..], (dump.len() - 1) as u32).change_context( - BlockChainTreeError::DerivativeChain(DerivChainErrorKind::FindByHeight), - )?; - - Ok(Some(block)) - } - - /// Get deserialized block by it's hash - pub fn find_by_hash(&self, hash: &[u8; 32]) -> Result, BlockChainTreeError> { - let height = match self - .height_reference - .get(hash) - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))? - { - None => { - return Ok(None); - } - Some(h) => { - u64::from_be_bytes(h.iter().copied().collect::>().try_into().unwrap()) - } - }; - - let block = - self.find_by_height(height) - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::FindByHash, - ))?; - - Ok(block) - } - - /// Dump config of the chain - pub fn dump_config(&self, root_path: &str) -> Result<(), BlockChainTreeError> { - let root = String::from(root_path); - let path_config = root + CONFIG_FILE; - - let mut file = File::create(path_config) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::DumpConfig, - )) - .attach_printable("failed to open config")?; - - file.write_all(&self.height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::DumpConfig, - )) - .attach_printable("failed to write height")?; - - file.write_all(&self.genesis_hash) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::DumpConfig, - )) - .attach_printable("failed to write genesis block")?; - - file.write_all(&self.difficulty) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::DumpConfig, - )) - .attach_printable("failed to write difficulty")?; - - file.write_all(&self.global_height.to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::DumpConfig, - )) - .attach_printable("failed to write global height")?; - - Ok(()) - } - - pub async fn flush(&self, root_path: &str) -> Result<(), BlockChainTreeError> { - self.dump_config(root_path)?; - - self.db - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to flush db")?; - - self.height_reference - .flush_async() - .await - .into_report() - .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) - .attach_printable("failed to flush db")?; - - Ok(()) - } - - /// Open chain without config, sets up all directories - pub fn without_config( - root_path: &str, - genesis_hash: &[u8; 32], - global_height: u64, - ) -> Result { - let root = String::from(root_path); - let path_blocks_st = root.clone() + BLOCKS_FOLDER; - let path_references_st = root + REFERENCES_FOLDER; - - let path_blocks = Path::new(&path_blocks_st); - let path_reference = Path::new(&path_references_st); - - // open blocks DB - let db = sled::open(path_blocks) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open blocks db")?; - - // open height references DB - let height_reference = sled::open(path_reference) - .into_report() - .change_context(BlockChainTreeError::DerivativeChain( - DerivChainErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open references db")?; - - Ok(DerivativeChain { - db, - height_reference, - height: 0, - genesis_hash: *genesis_hash, - difficulty: BEGINNING_DIFFICULTY, - global_height, - }) - } - - /// Get deserialized last block of the chain - pub fn get_last_block(&self) -> Result, BlockChainTreeError> { - self.find_by_height(self.height - 1) - } -} - -#[derive(Clone)] -pub struct BlockChainTree { - trxs_pool: Arc>, - summary_db: Arc>, - old_summary_db: Arc>, - main_chain: Arc, - deratives: Derivatives, -} - -impl BlockChainTree { - /// Open BlockChainTree - /// - /// opens blockchain tree with existing config - pub fn with_config() -> Result { - let summary_db_path = Path::new(&AMMOUNT_SUMMARY); - - // open summary db - let summary_db = sled::open(summary_db_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to open summary db")?; - - let old_summary_db_path = Path::new(&OLD_AMMOUNT_SUMMARY); - - // open old summary db - let old_summary_db = sled::open(old_summary_db_path) - .into_report() + self.old_gas_db = sled::open(path_gas_old) .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to open old summary db")?; - - // read transactions pool - let pool_path = String::from(BLOCKCHAIN_DIRECTORY) + TRANSACTIONS_POOL; - let pool_path = Path::new(&pool_path); - - let mut file = File::open(pool_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to open transactions pool")?; - - // read amount of transactions - let mut buf: [u8; 8] = [0; 8]; - file.read_exact(&mut buf) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to read amount of transactions")?; - - let trxs_amount = u64::from_be_bytes(buf); - - let mut buf: [u8; 4] = [0; 4]; - - // allocate VecDeque - let mut trxs_pool = TransactionsPool::with_capacity(10000); - - // parsing transactions - for _ in 0..trxs_amount { - file.read_exact(&mut buf) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to read transaction size")?; - - let tr_size = u32::from_be_bytes(buf); - - let mut transaction_buffer = vec![0u8; (tr_size - 1) as usize]; - - file.read_exact(&mut transaction_buffer) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init)) - .attach_printable("failed to read transaction")?; - - if transaction_buffer[0] == 0 { - let transaction = - Transaction::parse(&transaction_buffer[1..], (tr_size - 1) as u64) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::Init, - ))?; - - trxs_pool.push(Box::new(transaction)); - } else { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::Init, - )) - .attach_printable("Not implemented yet")); - } - } - - // opening main chain - let main_chain = Chain::new() - .change_context(BlockChainTreeError::BlockChainTree(BCTreeErrorKind::Init))?; - - Ok(BlockChainTree { - trxs_pool: Arc::new(RwLock::new(trxs_pool)), - summary_db: Arc::new(RwLock::new(SummaryDB::new(summary_db))), - main_chain: Arc::new(main_chain), - old_summary_db: Arc::new(RwLock::new(SummaryDB::new(old_summary_db))), - deratives: Arc::default(), - }) - } - - /// Open BlockChainTree - /// - /// opens blockchain tree without config - pub fn without_config() -> Result { - let summary_db_path = Path::new(&AMMOUNT_SUMMARY); - - // open summary db - let summary_db = sled::open(summary_db_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open summary db")?; - - // set initial value for the root address - if summary_db - .get(ROOT_PUBLIC_ADDRESS) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::InitWithoutConfig, - )) - .attach_printable( - "failed to get amount of coins in the summary db for the root address", - )? - .is_none() - { - let mut dump: Vec = Vec::with_capacity(tools::bigint_size(&COINS_PER_CYCLE)); - tools::dump_biguint(&COINS_PER_CYCLE, &mut dump).change_context( - BlockChainTreeError::BlockChainTree(BCTreeErrorKind::AddFunds), - )?; - summary_db - .insert(ROOT_PUBLIC_ADDRESS, dump) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::InitWithoutConfig, - )) - .attach_printable( - "failed to set amount of coins in the summary db for the root address", - )?; - } - - let old_summary_db_path = Path::new(&OLD_AMMOUNT_SUMMARY); - - // open old summary db - let old_summary_db = sled::open(old_summary_db_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open old summary db")?; - - // allocate VecDeque - let trxs_pool = TransactionsPool::with_capacity(10000); - - // opening main chain - let main_chain = Chain::new_without_config(MAIN_CHAIN_DIRECTORY, &GENESIS_BLOCK) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::InitWithoutConfig, - )) - .attach_printable("failed to open main chain")?; - - let _ = fs::create_dir(Path::new(DERIVATIVE_CHAINS_DIRECTORY)); - // .into_report() - // .change_context(BlockChainTreeError::BlockChainTree( - // BCTreeErrorKind::CreateDerivChain, - // )) - // .attach_printable("failed to create root folder for derivatives")?; - - Ok(BlockChainTree { - trxs_pool: Arc::new(RwLock::new(trxs_pool)), - summary_db: Arc::new(RwLock::new(SummaryDB::new(summary_db))), - main_chain: Arc::new(main_chain), - old_summary_db: Arc::new(RwLock::new(SummaryDB::new(old_summary_db))), - deratives: Arc::default(), - }) - } - - /// Dump Transactions pool - /// - /// Dumps Transactions pool into folder specified as static - pub async fn dump_pool(&self) -> Result<(), BlockChainTreeError> { - let pool_path = String::from(BLOCKCHAIN_DIRECTORY) + TRANSACTIONS_POOL; - let pool_path = Path::new(&pool_path); - - // open file - let mut file = File::create(pool_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::DumpPool, - )) - .attach_printable("failed to open config file")?; - - let trxs_pool = self.trxs_pool.read().await; - - // write transactions amount - file.write_all(&(trxs_pool.len() as u64).to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::DumpPool, - )) - .attach_printable("failed to write amount of transactions")?; - - //write transactions - for transaction in trxs_pool.transactions_iter() { - // get dump - let dump = transaction - .dump() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::DumpPool, - ))?; - - // write transaction size - file.write_all(&(dump.len() as u32).to_be_bytes()) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::DumpPool, - )) - .attach_printable("failed to write transaction size")?; - - // write transaction dump - file.write_all(&dump) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::DumpPool, - )) - .attach_printable("failed to write transaction dump")?; - } - - Ok(()) - } - - /// Flushes whole blockchain - /// - /// also dumps pool - pub async fn flush_blockchain(&self) -> Result<(), BlockChainTreeError> { - self.dump_pool().await?; - - self.main_chain.flush().await?; - - for (address, chain) in self.deratives.read().await.iter() { - let mut path_string = String::from(DERIVATIVE_CHAINS_DIRECTORY); - let hex_addr: String = address.encode_hex::(); - path_string += &hex_addr; - path_string += "/"; - - chain.read().await.flush(&path_string).await?; - } - - self.summary_db.read().await.flush().await?; - - Ok(()) - } - - /// Get derivative chain - /// - /// Gets existing derivative chain(checks by path), places into inner field `derivatives`, returnes pointer to chain - pub async fn get_derivative_chain( - &self, - addr: &[u8; 33], - ) -> Result>>, BlockChainTreeError> { - let mut path_string = String::from(DERIVATIVE_CHAINS_DIRECTORY); - let hex_addr: String = addr.encode_hex::(); - path_string += &hex_addr; - path_string += "/"; - - let path = Path::new(&path_string); - if path.exists() { - let result = DerivativeChain::new(&path_string).change_context( - BlockChainTreeError::BlockChainTree(BCTreeErrorKind::GetDerivChain), - )?; - - return Ok(Some( - self.deratives - .write() - .await - .entry(*addr) - .or_insert_with(|| Arc::new(RwLock::new(result))) - .clone(), - )); - } - - Ok(None) - } - - pub fn get_main_chain(&self) -> Arc { - self.main_chain.clone() - } - - /// Creates derivative chain - /// - /// Creates neccessary folders for derivative chain, creates chain, places into inner field `derivatives`, returns pointer to chain - pub async fn create_derivative_chain( - &self, - addr: &[u8; 33], - genesis_hash: &[u8; 32], - global_height: u64, - ) -> Result>, BlockChainTreeError> { - let mut root_path = String::from(DERIVATIVE_CHAINS_DIRECTORY); - let hex_addr: String = addr.encode_hex::(); - root_path += &hex_addr; - root_path += "/"; - - fs::create_dir(Path::new(&root_path)) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CreateDerivChain, - )) - .attach_printable("failed to create root folder")?; - - let blocks_path = root_path.clone() + BLOCKS_FOLDER; - fs::create_dir(Path::new(&blocks_path)) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CreateDerivChain, - )) - .attach_printable("failed to create blocks folder")?; - - let references_path = root_path.clone() + REFERENCES_FOLDER; - fs::create_dir(Path::new(&references_path)) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CreateDerivChain, - )) - .attach_printable("failed to create references folder")?; - - let chain = DerivativeChain::without_config(&root_path, genesis_hash, global_height) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CreateDerivChain, - ))?; - - chain - .dump_config(&root_path) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CreateDerivChain, - ))?; - - return Ok(self - .deratives - .write() - .await - .entry(*addr) - .or_insert_with(|| Arc::new(RwLock::new(chain))) - .clone()); - } - - /// Check main folders for BlockChainTree - /// - /// Checks for required folders, if some not found will create them - pub fn check_main_folders() -> Result<(), BlockChainTreeError> { - let root = Path::new(BLOCKCHAIN_DIRECTORY); - if !root.exists() { - fs::create_dir(root) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create blockchain root")?; - } - - let main_path = Path::new(MAIN_CHAIN_DIRECTORY); - if !main_path.exists() { - fs::create_dir(main_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create main chain folder")?; - } - - let summary_path = Path::new(AMMOUNT_SUMMARY); - if !summary_path.exists() { - fs::create_dir(summary_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create summary folder")?; - } - - let old_summary_path = Path::new(OLD_AMMOUNT_SUMMARY); - if !old_summary_path.exists() { - fs::create_dir(old_summary_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create old summary folder")?; - } - - let blocks_path = String::from(MAIN_CHAIN_DIRECTORY) + BLOCKS_FOLDER; - let blocks_path = Path::new(&blocks_path); - if !blocks_path.exists() { - fs::create_dir(blocks_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create blocks path")?; - } - - let references_path = String::from(MAIN_CHAIN_DIRECTORY) + REFERENCES_FOLDER; - let references_path = Path::new(&references_path); - if !references_path.exists() { - fs::create_dir(references_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create references paths")?; - } - - let transactions_path = String::from(MAIN_CHAIN_DIRECTORY) + TRANSACTIONS_FOLDER; - let transactions_path = Path::new(&transactions_path); - if !transactions_path.exists() { - fs::create_dir(references_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create transactions paths")?; - } - - let derivatives_path = String::from(DERIVATIVE_CHAINS_DIRECTORY); - let derivatives_path = Path::new(&derivatives_path); - if !derivatives_path.exists() { - fs::create_dir(derivatives_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create derivatives chains path")?; - } - - let derivative_chains_path = String::from(DERIVATIVE_CHAINS_DIRECTORY) + CHAINS_FOLDER; - let derivative_chains_path = Path::new(&derivative_chains_path); - if !derivative_chains_path.exists() { - fs::create_dir(derivative_chains_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::CheckMainFolders, - )) - .attach_printable("failed to create derivative chains folder")?; - } - - Ok(()) - } - - // summary data bases functions - - /// Add funds for address - /// - /// Adds funs for specified address in the summary db - pub async fn add_funds( - &self, - addr: &[u8; 33], - funds: &BigUint, - ) -> Result<(), BlockChainTreeError> { - self.summary_db.write().await.add_funds(addr, funds).await - } - - /// Decrease funds - /// - /// Decreases funds for specified address in the summary db - pub async fn decrease_funds( - &self, - addr: &[u8; 33], - funds: &BigUint, - ) -> Result<(), BlockChainTreeError> { - self.summary_db - .write() - .await - .decrease_funds(addr, funds) - .await - } - - /// Get funds - /// - /// Gets funds for specified address from summary db - pub async fn get_funds(&self, addr: &[u8; 33]) -> Result { - self.summary_db.read().await.get_funds(addr) - } - - /// Get old funds - /// - /// Gets old funds for specified address from previous summary db - pub async fn get_old_funds(&self, addr: &[u8; 33]) -> Result { - self.old_summary_db.read().await.get_funds(addr) - } - - /// Move current summary database to old database - /// - /// Removes old summary database and places current summary db on it's place - pub fn move_summary_database(&self) -> Result<(Db, Db), BlockChainTreeError> { - let old_sum_path = Path::new(OLD_AMMOUNT_SUMMARY); - let sum_path = Path::new(AMMOUNT_SUMMARY); - - //self.old_summary_db = Arc::new(None); - //self.summary_db = Arc::new(None); - - fs::remove_dir_all(old_sum_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::MoveSummaryDB, - )) - .attach_printable("failed to remove previous database")?; - - fs::create_dir(old_sum_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::MoveSummaryDB, - )) - .attach_printable("failed to create folder for an old summarize db")?; - - tools::copy_dir_all(sum_path, old_sum_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::MoveSummaryDB, - )) - .attach_printable("failed to copy current db into old db")?; - - let summary_db = sled::open(sum_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::MoveSummaryDB, - )) - .attach_printable("failed to open summary db")?; - - //self.summary_db = Arc::new(Some(result)); - - let old_summary_db = sled::open(old_sum_path) - .into_report() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::MoveSummaryDB, - )) - .attach_printable("failed to open old summary db")?; - - //self.old_summary_db = Arc::new(Some(result)); - - Ok((summary_db, old_summary_db)) - } - - // Check whether transaction with same hash exists - // - // First check in trxs_hashes then in main chain references - // - // Blocks trxs pool for reading for the whole duration of the function - pub async fn transaction_exists(&self, hash: &[u8; 32]) -> Result { - let trxs_pool = self.trxs_pool.read().await; - if trxs_pool.transaction_exists(hash) { - return Ok(true); - } - - if self - .get_main_chain() - .transaction_exists(hash) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))? - { - return Ok(true); - } - - Ok(false) - } - - /// Add new transaction - /// - /// Adds new transaction to the transaction pool - /// - /// If it's not the last block of epoch transaction will be immediately processed - /// - /// If transaction with same hash exists will return error - pub async fn new_transaction(&self, tr: Transaction) -> Result<(), BlockChainTreeError> { - let mut trxs_pool = self.trxs_pool.write().await; - - let tr_hash = tr.hash(); - if trxs_pool.transaction_exists(&tr_hash) - || self - .get_main_chain() - .transaction_exists(&tr_hash) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))? - { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable("Transaction with same hash found")); - } - - if !tr - .verify() - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable(format!( - "Unable to verify transaction with hash: {:?}", - tr.hash() - ))? - { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable("Transaction verification failed")); - } - - let difficulty = self.main_chain.difficulty.read().await; - let fee = Chain::calculate_fee(&difficulty); - drop(difficulty); - - let amount = tr.get_amount(); - - if amount <= &fee { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable("Amount sent in transaction is smaller, than the fee")); - } - - trxs_pool.push(Box::new(tr.clone())); - - self.decrease_funds(tr.get_sender(), amount) - .await - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))?; - - self.add_funds(tr.get_sender(), &(amount - &fee)) - .await - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))?; - - Ok(()) - } - - /// Add transaction directly to the chain - /// - /// sets amounts in summarize db - async fn add_transaction( - &self, - transaction: &Transaction, - fee: &BigUint, - ) -> Result<(), BlockChainTreeError> { - let tr_hash = transaction.hash(); - if self - .get_main_chain() - .transaction_exists(&tr_hash) - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))? - { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable("Transaction with same hash found")); - } - - let amount = transaction.get_amount(); - - if amount <= fee { - return Err(Report::new(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - )) - .attach_printable("Amount sent in transaction is smaller, than the fee")); - } - - self.decrease_funds(transaction.get_sender(), amount) - .await - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))?; - - self.add_funds(transaction.get_sender(), &(amount - fee)) - .await - .change_context(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::NewTransaction, - ))?; - - self.main_chain.add_transaction_raw(transaction).await?; - - Ok(()) - } - - /// Create transaction block - /// - /// This function validates pow, pops transactions from trxs_pool, then - /// - /// adds new transactions block and poped transactions to the main chain - async fn emit_transaction_block( - &self, - pow: &[u8], - addr: [u8; 33], - timestamp: u64, - difficulty: [u8; 32], - ) -> Result { - let mut trxs_pool = self.trxs_pool.write().await; - - let last_hash = self.main_chain.get_last_hash().await.change_context( - BlockChainTreeError::BlockChainTree(BCTreeErrorKind::CreateMainChainBlock), - )?; - - if !tools::check_pow(&last_hash, &difficulty, pow) { - // if pow is bad - return Err(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::WrongPow, - )) - .into_report(); - } - - let fee = Chain::calculate_fee(&difficulty); - - let transactions_amount = trxs_pool.len(); - - // get transactions - let mut transactions: Vec> = - Vec::with_capacity(transactions_amount + 1); - - // founder transaction - let founder_transaction_amount = (transactions_amount * &fee) - + if self.get_funds(&ROOT_PUBLIC_ADDRESS).await? >= *MAIN_CHAIN_PAYMENT { - // if there is enough coins left in the root address make payment transaction - self.decrease_funds(&ROOT_PUBLIC_ADDRESS, &MAIN_CHAIN_PAYMENT) - .await?; - MAIN_CHAIN_PAYMENT.clone() - } else { - 0usize.into() - }; - - transactions.push(Box::new(Transaction::new( - ROOT_PUBLIC_ADDRESS, - addr, - timestamp, - founder_transaction_amount.clone(), - ROOT_PRIVATE_ADDRESS, - ))); - - self.add_funds(&addr, &founder_transaction_amount).await?; - - transactions.extend( - (0..transactions_amount).map(|_| unsafe { trxs_pool.pop().unwrap_unchecked().1 }), - ); - - // get hashes & remove transaction references - let transactions_hashes: Vec<_> = transactions.iter().map(|trx| trx.hash()).collect(); - - // build merkle tree & get root - let merkle_tree = MerkleTree::build_tree(&transactions_hashes); - let merkle_tree_root = *merkle_tree.get_root(); - - let basic_info = BasicInfo::new( - timestamp, - pow.to_vec(), - last_hash, - self.main_chain.get_height().await, - difficulty, - addr, - ); - - // add block to the main chain - let block = TransactionBlock::new(transactions_hashes, fee, basic_info, merkle_tree_root); - self.main_chain.add_block_raw(&block).await?; - - // add transactions to the main chain - self.main_chain.add_transactions_raw(transactions).await?; - - Ok(block) - } - - async fn emit_summarize_block( - &self, - pow: &[u8], - addr: [u8; 33], - timestamp: u64, - difficulty: [u8; 32], - ) -> Result { - let last_hash = self.main_chain.get_last_hash().await.change_context( - BlockChainTreeError::BlockChainTree(BCTreeErrorKind::CreateMainChainBlock), - )?; - - if !tools::check_pow(&last_hash, &difficulty, pow) { - // if pow is bad - return Err(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::WrongPow, - )) - .into_report(); - } - - let basic_info = BasicInfo::new( - timestamp, - pow.to_vec(), - last_hash, - self.main_chain.get_height().await, - difficulty, - addr, - ); - - let founder_transaction = Transaction::new( - ROOT_PUBLIC_ADDRESS, - addr, - timestamp, - MAIN_CHAIN_PAYMENT.clone(), - ROOT_PRIVATE_ADDRESS, - ); - - self.add_funds(&addr, &MAIN_CHAIN_PAYMENT).await?; - - let block = SummarizeBlock::new(basic_info, founder_transaction.hash()); - - self.main_chain.add_block_raw(&block).await?; - self.main_chain - .add_transaction_raw(&founder_transaction) - .await?; - - Ok(block) - } - - /// Set new difficulty for the chain - /// - /// height - curent chains height, last block's height - pub async fn new_main_chain_difficulty( - &self, - timestamp: u64, - difficulty: &mut [u8; 32], - height: u64, - ) -> Result<(), BlockChainTreeError> { - // TODO: rewrite the way difficulty calculated - if *difficulty != MAX_DIFFICULTY { - let last_block = self.main_chain.find_by_height(height - 1).await?; - if let Some(last_block) = last_block { - let last_block_timestamp = last_block.get_info().timestamp; - match (timestamp - last_block_timestamp).cmp(&600) { - std::cmp::Ordering::Less => { - for byte in difficulty.iter_mut() { - if *byte > 0 { - *byte <<= 1; - break; - } - } - } - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => { - let mut index: usize = 0; - for (ind, byte) in difficulty.iter().enumerate() { - let byte = *byte; - if byte > 0 { - if byte == 0xFF && ind > 0 { - index = ind - 1; - break; - } - index = ind; - break; - } - } - - difficulty[index] = (difficulty[index] >> 1) | 0b10000000; - } - } - } - } - Ok(()) - } - - /// Create main chain block and add it to the main chain - /// - /// Verifies POW and creates new main chain block - /// - /// Does not verify timestamp - /// - /// returns emmited block - pub async fn emit_main_chain_block( - &self, - pow: &[u8], - addr: [u8; 33], - timestamp: u64, - ) -> Result, BlockChainTreeError> { - let mut difficulty = self.main_chain.get_locked_difficulty().await; - let height = self.main_chain.get_height().await as usize; - let block: Arc = - if height % BLOCKS_PER_ITERATION == 0 && height > 0 { - // new cycle - let block = self - .emit_summarize_block(pow, addr, timestamp, *difficulty) - .await?; - - let mut summary_db_lock = self.summary_db.write().await; - let mut old_summary_db_lock = self.old_summary_db.write().await; - - let (summary_db, old_summary_db) = self.move_summary_database()?; - - *summary_db_lock = SummaryDB::new(summary_db); - *old_summary_db_lock = SummaryDB::new(old_summary_db); - - Arc::new(block) - } else { - let block = self - .emit_transaction_block(pow, addr, timestamp, *difficulty) - .await?; - - Arc::new(block) - }; - - self.new_main_chain_difficulty(timestamp, &mut difficulty, height as u64) - .await?; - - Ok(block) - } - - /// Adds new block, checks for block's validity - /// - /// returns true is block was added/already present - /// - /// returns false if the block is valid, but there is already a block there or it has diverging transactions - /// - /// returns error if the block couldn't be verified - pub async fn new_main_chain_block( - &self, - new_block: &MainChainBlockArc, - ) -> Result { - let mut difficulty = self.main_chain.difficulty.write().await; - let mut trxs_pool = self.trxs_pool.write().await; - - let height = *self.main_chain.height.read().await; - let new_block_height = new_block.get_info().height; - let new_block_hash = new_block.hash().change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if new_block_height == 0 { - return Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Tried to add block with height 0"), - ); - } - - match new_block_height.cmp(&height) { - Ordering::Less => { - // not the last block - let current_block = match self.main_chain.find_by_height(new_block_height).await? { - Some(block) => block, - None => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - ))); - } - }; - let current_block_hash = - current_block - .hash() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if current_block_hash == new_block_hash { - return Ok(true); - } - - let prev_block = match self.main_chain.find_by_height(new_block_height - 1).await? { - Some(block) => block, - None => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - ))); - } - }; - let prev_block_hash = - prev_block - .hash() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if !new_block.verify_block(&prev_block.hash().change_context( - BlockChainTreeError::Chain(ChainErrorKind::FailedToHashBlock), - )?) { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable("Wrong previous hash")); - } - - if !check_pow( - &prev_block_hash, - ¤t_block.get_info().difficulty, - &new_block.get_info().pow, - ) { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable("Bad POW")); - } - - return Ok(false); - } - Ordering::Equal => { - // the last block - let last_hash = self - .main_chain - .get_last_hash() - .await - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Couldn't find last hash")?; - - // verify new block with prev hash - if !new_block.verify_block(&last_hash) { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable("Wrong previous hash")); - } - - // verify new blck's pow - if !tools::check_pow(&last_hash, &difficulty, &new_block.get_info().pow) { - // if pow is bad - return Err(BlockChainTreeError::BlockChainTree( - BCTreeErrorKind::WrongPow, - )) - .into_report(); - } - - // get last block of the chain - let last_block = self - .main_chain - .get_last_block() - .await - .change_context(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Couldn't find last block")? - .expect( - "Something went horribly wrong, couldn't find last block in main chain", - ); - - // check new block's timestamp - match new_block - .get_info() - .timestamp - .cmp(&last_block.get_info().timestamp) - { - Ordering::Less | Ordering::Equal => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable("The block is older, than the last block")); - } - _ => {} - } - - if height as usize % BLOCKS_PER_ITERATION == 0 { - // summarize block - if new_block.get_transactions().len() != 1 { - return Err(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .into_report(); - } - let founder_transaction = Transaction::new( - ROOT_PUBLIC_ADDRESS, - *new_block.get_founder(), - new_block.get_info().timestamp, - MAIN_CHAIN_PAYMENT.clone(), - ROOT_PRIVATE_ADDRESS, - ); - let constructed_block = SummarizeBlock::new( - BasicInfo::new( - new_block.get_info().timestamp, - new_block.get_info().pow, - last_hash, - height, - *difficulty, - *new_block.get_founder(), - ), - founder_transaction.hash(), - ); - - if !new_block - .get_merkle_root() - .eq(&constructed_block.get_merkle_root()) - { - return Err(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .into_report() - .attach_printable("The merkle root is wrong"); - } - - self.add_funds(new_block.get_founder(), &MAIN_CHAIN_PAYMENT) - .await?; - - self.main_chain.add_block_raw(&constructed_block).await?; - self.main_chain - .add_transaction_raw(&founder_transaction) - .await?; - } else { - let transactions_amount = trxs_pool.len(); - - //let new_block_transactions = new_block.get_transactions(); - - let new_block_info = new_block.get_info(); - - let mut transactions_hashes: Vec<[u8; 32]> = - Vec::with_capacity(transactions_amount + 1); - - let fee = Chain::calculate_fee(&difficulty); - - let mut decrease_root_funds = false; - - // founder transaction - let founder_transaction_amount = (transactions_amount * &fee) - + if self.get_funds(&ROOT_PUBLIC_ADDRESS).await? >= *MAIN_CHAIN_PAYMENT { - // if there is enough coins left in the root address make payment transaction - decrease_root_funds = true; - MAIN_CHAIN_PAYMENT.clone() - } else { - 0usize.into() - }; - - let founder_transaction = Transaction::new( - ROOT_PUBLIC_ADDRESS, - new_block_info.founder, - new_block_info.timestamp, - founder_transaction_amount.clone(), - ROOT_PRIVATE_ADDRESS, - ); - - transactions_hashes.push(founder_transaction.hash()); - - // get sorted transactions - let mut transactions: Vec<_> = trxs_pool.pool.iter().collect(); - transactions.sort_by(|a, b| b.cmp(a)); - transactions_hashes.extend(transactions.iter().map(|tr| tr.hash())); - - drop(transactions); // drop cuz not needed anymore - - // construct new block from new_block data - let mut constructed_block = TransactionBlock::new( - transactions_hashes, - fee, - BasicInfo::new( - new_block_info.timestamp, - new_block_info.pow, - last_hash, - height, - *difficulty, - new_block_info.founder, - ), - new_block.get_merkle_root(), - ); - - // verify transactions - if !constructed_block.check_merkle_tree().map_err(|e| { - e.change_context(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - })? { - return Ok(false); - } - - // all checks passed, proceed to add block - if decrease_root_funds { - self.decrease_funds(&ROOT_PUBLIC_ADDRESS, &MAIN_CHAIN_PAYMENT) - .await?; - } - - self.add_funds(&new_block_info.founder, &founder_transaction_amount) - .await?; - - // add founder transaction - self.main_chain - .add_transaction_raw(&founder_transaction) - .await?; - - // gather transactions from the pool - let transactions: Vec<_> = (0..transactions_amount) - .map(|_| unsafe { trxs_pool.pop().unwrap_unchecked().1 }) - .collect(); - - // add transactions from the pool - self.main_chain.add_transactions_raw(transactions).await?; - - // add block - self.main_chain.add_block_raw(&constructed_block).await?; - } - - self.new_main_chain_difficulty( - new_block.get_info().timestamp, - &mut difficulty, - height, - ) - .await?; - } - Ordering::Greater => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - )) - .attach_printable("The block has bigger height, than the current chains height")); - } - } - - Ok(true) - } - - /// Overwrites the block with same heigh if it existed - /// - /// also removes all higher blocks, linked transactions and derivative chains - /// - /// clears transactions pool - /// - /// transactions should be sorted and verify beforehand - pub async fn overwrite_main_chain_block( - &self, - new_block: &MainChainBlockArc, - transactions: &[Transaction], - ) -> Result<(), BlockChainTreeError> { - let mut difficulty = self.main_chain.difficulty.write().await; - let mut trxs_pool = self.trxs_pool.write().await; - - let new_block_height = new_block.get_info().height; - let new_block_hash = new_block.hash().change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if new_block_height == 0 { - return Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Tried to add block with height 0"), - ); - } - - let current_block = match self.main_chain.find_by_height(new_block_height).await? { - Some(block) => block, - None => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - ))); - } - }; - let current_block_hash = - current_block - .hash() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if current_block_hash == new_block_hash { - return Ok(()); - } - - let prev_block = match self.main_chain.find_by_height(new_block_height - 1).await? { - Some(block) => block, - None => { - return Err(Report::new(BlockChainTreeError::Chain( - ChainErrorKind::FailedToVerify, - ))); - } - }; - let prev_block_hash = prev_block - .hash() - .change_context(BlockChainTreeError::Chain( - ChainErrorKind::FailedToHashBlock, - ))?; - - if !new_block.verify_block(&prev_block.hash().change_context( - BlockChainTreeError::Chain(ChainErrorKind::FailedToHashBlock), - )?) { - return Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Wrong previous hash"), - ); - } - - if !check_pow( - &prev_block_hash, - ¤t_block.get_info().difficulty, - &new_block.get_info().pow, - ) { - return Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("Bad POW"), - ); - } - - let fee = Chain::calculate_fee(&new_block.get_info().difficulty); - - // founder transaction - let founder_transaction_amount = (transactions.len() * &fee) - + if self.get_funds(&ROOT_PUBLIC_ADDRESS).await? >= *MAIN_CHAIN_PAYMENT { - // if there is enough coins left in the root address make payment transaction - self.decrease_funds(&ROOT_PUBLIC_ADDRESS, &MAIN_CHAIN_PAYMENT) - .await?; - MAIN_CHAIN_PAYMENT.clone() - } else { - 0usize.into() - }; - - let founder_transaction = Transaction::new( - ROOT_PUBLIC_ADDRESS, - *new_block.get_founder(), - new_block.get_info().timestamp, - founder_transaction_amount, - ROOT_PRIVATE_ADDRESS, - ); - - // verify merkle tree - let mut transactions_hashes: Vec<[u8; 32]> = Vec::with_capacity(transactions.len() + 1); - transactions_hashes.push(founder_transaction.hash()); - transactions_hashes.extend(transactions.iter().map(|t| t.hash())); - - let merkle_tree = MerkleTree::build_tree(&transactions_hashes); - //merkle_tree.add_objects(&transactions_hashes); - let calculated_merkle_tree_root = merkle_tree.get_root(); - - if !new_block.get_merkle_root().eq(calculated_merkle_tree_root) { - return Err( - Report::new(BlockChainTreeError::Chain(ChainErrorKind::FailedToVerify)) - .attach_printable("The provided in block merkle tree root is not equal to the supplied transactions"), - ); - } - - let summary_db = self.summary_db.read().await; - self.main_chain - .block_overwrite(new_block, &summary_db) - .await?; - - // add transations - self.add_transaction(&founder_transaction, &fee).await?; - - for transaction in transactions { - self.add_transaction(transaction, &fee).await?; - } - - // clear txs pool - trxs_pool.pool.clear(); - - // recalculate difficulty - self.new_main_chain_difficulty( - new_block.get_info().timestamp, - &mut difficulty, - new_block_height, - ) - .await?; + .attach_printable("failed to open old gas db")?; Ok(()) } diff --git a/src/chain.rs b/src/chain.rs new file mode 100644 index 0000000..0e2d5cd --- /dev/null +++ b/src/chain.rs @@ -0,0 +1,712 @@ +use std::{fs::File, io::Read, path::Path, sync::Arc}; + +use async_trait::async_trait; +use error_stack::{Report, ResultExt}; +use parking_lot::RwLock; +use primitive_types::U256; +use sled::Db; +use tokio::{fs::OpenOptions, io::AsyncWriteExt}; + +use crate::block::{BlockArc, DerivativeBlock}; +use crate::dump_headers::Headers; +use crate::{ + block::{self, BasicInfo, Block, SummarizeBlock}, + errors::{BlockChainTreeError, ChainErrorKind}, + merkletree::MerkleTree, + tools, + transaction::Transactionable, +}; +use crate::{static_values::*, transaction}; + +#[async_trait] +pub trait Chain { + async fn dump_config(&self) -> Result<(), Report>; + async fn flush(&self) -> Result<(), Report>; + async fn add_block( + &self, + block: &(impl Block + Sync), + ) -> Result<(), Report>; + async fn find_raw_by_height( + &self, + height: &U256, + ) -> Result>, Report>; + async fn find_raw_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report>; + async fn get_last_raw_block(&self) -> Result>, Report>; + async fn get_last_block( + &self, + ) -> Result>, Report>; + async fn find_by_height( + &self, + height: &U256, + ) -> Result>, Report>; + async fn find_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report>; +} + +#[derive(Clone)] +pub struct MainChain { + blocks: Db, + height_reference: Db, + transactions: Db, + height: Arc>, + difficulty: Arc>, +} + +impl MainChain { + pub fn new() -> Result> { + let root = String::from(MAIN_CHAIN_DIRECTORY); + + let path_blocks_st = root.clone() + BLOCKS_FOLDER; + let path_references_st = root.clone() + REFERENCES_FOLDER; + let path_transactions_st = root.clone() + TRANSACTIONS_FOLDER; + let path_height_st = root + CONFIG_FILE; + + let path_blocks = Path::new(&path_blocks_st); + let path_reference = Path::new(&path_references_st); + let path_transactions = Path::new(&path_transactions_st); + let path_height = Path::new(&path_height_st); + + // open blocks DB + let blocks = sled::open(path_blocks) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to open blocks db")?; + + // open height references DB + let height_reference = sled::open(path_reference) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to open references db")?; + + // open transactions DB + let transactions = sled::open(path_transactions) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to open transactions db")?; + + let file = File::open(path_height); + + let (height, difficulty) = if let Ok(mut file) = file { + let mut height_bytes: [u8; 32] = [0; 32]; + file.read_exact(&mut height_bytes) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to read config")?; + + // read difficulty + let mut difficulty: [u8; 32] = [0; 32]; + file.read_exact(&mut difficulty) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to read difficulty")?; + + (U256::from_big_endian(&height_bytes), difficulty) + } else { + (U256::zero(), BEGINNING_DIFFICULTY) + }; + + let chain = Self { + blocks, + height_reference, + transactions, + height: Arc::new(RwLock::new(height)), + difficulty: Arc::new(RwLock::new(difficulty)), + }; + if height.is_zero() { + let info = BasicInfo::new( + INCEPTION_TIMESTAMP, + [0; 32], + [0u8; 32], + U256::zero(), + BEGINNING_DIFFICULTY, + ROOT_PUBLIC_ADDRESS, + ); + let mut initial_amount = Vec::::new(); + initial_amount.extend(ROOT_PUBLIC_ADDRESS.iter()); + initial_amount.push(b'|'); + initial_amount.extend(COINS_PER_CYCLE.to_string().as_bytes().iter()); + initial_amount.push(b'|'); + initial_amount.push(b'0'); + + let merkle_tree = MerkleTree::build_tree(&[tools::hash(&initial_amount)]); + chain + .add_block(Arc::new(SummarizeBlock { + default_info: info, + merkle_tree_root: *merkle_tree.get_root(), + })) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("Failed to insert inception block")?; + } + + Ok(chain) + } + /// Dump config + /// + /// Dumps chain's config + async fn dump_config(&self) -> Result<(), Report> { + let root = String::from(MAIN_CHAIN_DIRECTORY); + let path_config = root + CONFIG_FILE; + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path_config) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig))?; + let mut buffer_32_bytes: [u8; 32] = [0; 32]; + self.height.read().to_big_endian(&mut buffer_32_bytes); + file.write_all(&buffer_32_bytes) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to write height")?; + + file.write_all(self.difficulty.read().as_ref()) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to write difficulty")?; + + Ok(()) + } + + pub fn get_height(&self) -> U256 { + *self.height.read() + } + + /// Flushes all DBs and config + pub async fn flush(&self) -> Result<(), Report> { + self.dump_config().await?; + + self.blocks + .flush_async() + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to flush db")?; + + self.height_reference + .flush_async() + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to flush height references")?; + + self.transactions + .flush_async() + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to flush transactions")?; + + Ok(()) + } + + pub fn add_transactions( + &self, + transactions: &[impl transaction::Transactionable], + ) -> Result<(), Report> { + for transaction in transactions { + let dump = transaction + .dump() + .change_context(BlockChainTreeError::Chain( + ChainErrorKind::AddingTransaction, + ))?; + self.transactions + .insert(tools::hash(&dump), dump) + .change_context(BlockChainTreeError::Chain( + ChainErrorKind::AddingTransaction, + )) + .attach_printable("Failed to insert transaction")?; + } + Ok(()) + } + + pub fn transaction_exists( + &self, + transaction_hash: &[u8; 32], + ) -> Result> { + self.transactions + .contains_key(transaction_hash) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE)) + } + + pub fn get_transaction_raw( + &self, + transaction_hash: &[u8; 32], + ) -> Result>, Report> { + let transaction = self + .transactions + .get(transaction_hash) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; + Ok(transaction.map(|v| v.to_vec())) + } + + pub fn get_transaction( + &self, + transaction_hash: &[u8; 32], + ) -> Result, Report> { + let raw_transaction = self.get_transaction_raw(transaction_hash)?; + + if let Some(tr) = raw_transaction { + if !tr.first().unwrap_or(&10).eq(&(Headers::Transaction as u8)) { + return Err(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE).into()); + } + return Ok(Some( + transaction::Transaction::parse(&tr[1..]) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?, + )); + } + + Ok(None) + } + + /// Adds new block to the chain db + /// + /// Adds block and sets height reference for it + /// + /// Checks for blocks validity, adds it directly to the end of the chain + pub fn add_block(&self, block: BlockArc) -> Result<(), Report> { + let dump = block + .dump() + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + + let hash = tools::hash(&dump); + + let mut height = self.height.write(); + + if block.get_info().height != *height { + return Err(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)).attach_printable( + "The height of the chain is different from the height of the block", + ); + } + + let mut height_bytes = [0u8; 32]; + height.to_big_endian(&mut height_bytes); + + self.blocks + .insert(height_bytes, dump) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)) + .attach_printable("Failed to insert block to blocks db")?; + + self.height_reference + .insert(hash, &height_bytes) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)) + .attach_printable("Failed to insert height reference for the block")?; + + *height += U256::one(); + + Ok(()) + } + + /// Get serialized block by it's height + pub fn find_raw_by_height( + &self, + height: &U256, + ) -> Result>, Report> { + let chain_height = self.height.read(); + if height > &chain_height { + return Ok(None); + } + drop(chain_height); + + let mut height_serialized = [0u8; 32]; + height.to_big_endian(&mut height_serialized); + let mut dump = self + .blocks + .get(height_serialized) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))?; + + if let Some(dump) = dump.take() { + return Ok(Some(dump.to_vec())); + } + Ok(None) + } + + /// Get serialized block by it's hash + pub fn find_raw_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report> { + let height = match self + .height_reference + .get(hash) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))? + { + None => { + return Ok(None); + } + Some(h) => U256::from_big_endian(&h.iter().copied().collect::>()), + }; + + let block = self + .find_raw_by_height(&height) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; + + Ok(block) + } + + pub fn find_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report> { + let dump = self.find_raw_by_hash(hash)?; + + let deserialized = if let Some(data) = dump { + Some( + block::deserialize_main_chain_block(&data) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable(format!( + "Failed to deserialize latest main chain block with hash {:?}", + hash + ))?, + ) + } else { + None + }; + + Ok(deserialized) + } + + /// Get serialized last block of the chain + pub fn get_last_raw_block(&self) -> Result>, Report> { + let height = self.height.read(); + let last_block_index = *height - 1; + drop(height); + + self.find_raw_by_height(&last_block_index) + } + + /// Get deserialized latest block + pub fn get_last_block( + &self, + ) -> Result>, Report> { + let dump = self.get_last_raw_block()?; + + let deserialized = if let Some(data) = dump { + Some( + block::deserialize_main_chain_block(&data) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable("Failed to deserialize latest main chain block")?, + ) + } else { + None + }; + + Ok(deserialized) + } + + /// Get deserialized block by height + pub fn find_by_height( + &self, + height: &U256, + ) -> Result>, Report> { + let dump = self.find_raw_by_height(height)?; + + let deserialized = if let Some(data) = dump { + Some( + block::deserialize_main_chain_block(&data) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable(format!( + "Failed to deserialize main chain block with height {}", + height + ))?, + ) + } else { + None + }; + + Ok(deserialized) + } +} + +#[derive(Clone)] +pub struct DerivativeChain { + blocks: Db, + height_reference: Db, + height: Arc>, + pub genesis_hash: Arc<[u8; 32]>, + difficulty: Arc>, + chain_owner: String, +} + +impl DerivativeChain { + pub fn new( + chain_owner: &str, + provided_genesis_hash: &[u8; 32], + ) -> Result> { + let root = String::from(DERIVATIVE_CHAINS_DIRECTORY) + chain_owner + "/"; + + let path_blocks_st = root.clone() + BLOCKS_FOLDER; + let path_references_st = root.clone() + REFERENCES_FOLDER; + let path_height_st = root + CONFIG_FILE; + + let path_blocks = Path::new(&path_blocks_st); + let path_reference = Path::new(&path_references_st); + let path_height = Path::new(&path_height_st); + + // open blocks DB + let blocks = sled::open(path_blocks) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to open blocks db")?; + + // open height references DB + let height_reference = sled::open(path_reference) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to open references db")?; + + let file = File::open(path_height); + + let (height, difficulty, genesis_hash) = if let Ok(mut file) = file { + let mut height_bytes: [u8; 32] = [0; 32]; + file.read_exact(&mut height_bytes) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to read config")?; + + // read difficulty + let mut difficulty: [u8; 32] = [0; 32]; + file.read_exact(&mut difficulty) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to read difficulty")?; + + // read difficulty + let mut genesis_hash: [u8; 32] = [0; 32]; + file.read_exact(&mut genesis_hash) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::Init)) + .attach_printable("failed to read genesis hash")?; + + ( + U256::from_big_endian(&height_bytes), + difficulty, + genesis_hash, + ) + } else { + (U256::zero(), BEGINNING_DIFFICULTY, *provided_genesis_hash) + }; + + let chain = Self { + blocks, + height_reference, + height: Arc::new(RwLock::new(height)), + difficulty: Arc::new(RwLock::new(difficulty)), + genesis_hash: Arc::new(genesis_hash), + chain_owner: chain_owner.to_string(), + }; + + Ok(chain) + } + + pub fn get_height(&self) -> U256 { + *self.height.read() + } + + /// Dump config + /// + /// Dumps chain's config + async fn dump_config(&self) -> Result<(), Report> { + let root = String::from(DERIVATIVE_CHAINS_DIRECTORY) + &self.chain_owner + "/"; + let path_config = root + CONFIG_FILE; + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path_config) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig))?; + let mut buffer_32_bytes: [u8; 32] = [0; 32]; + self.height.read().to_big_endian(&mut buffer_32_bytes); + file.write_all(&buffer_32_bytes) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to write height")?; + + file.write_all(self.difficulty.read().as_ref()) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to write difficulty")?; + + file.write_all(self.genesis_hash.as_ref()) + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to write genesis hash")?; + + Ok(()) + } + + /// Flushes all DBs and config + pub async fn flush(&self) -> Result<(), Report> { + self.dump_config().await?; + + self.blocks + .flush_async() + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to flush db")?; + + self.height_reference + .flush_async() + .await + .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) + .attach_printable("failed to flush height references")?; + + Ok(()) + } + + /// Adds new block to the chain db + /// + /// Adds block and sets heigh reference for it + /// + /// Checks for blocks validity, adds it directly to the end of the chain + pub fn add_block(&self, block: &DerivativeBlock) -> Result<(), Report> { + let dump = block + .dump() + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock))?; + + let hash = tools::hash(&dump); + + let mut height = self.height.write(); + + if block.get_info().height != *height + 1 { + return Err(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)).attach_printable( + "The height of the chain is different from the height of the block", + ); + } + + let mut height_bytes = [0u8; 32]; + height.to_big_endian(&mut height_bytes); + + self.blocks + .insert(height_bytes, dump) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)) + .attach_printable("Failed to insert block to blocks db")?; + + self.height_reference + .insert(hash, &height_bytes) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::AddingBlock)) + .attach_printable("Failed to insert height reference for the block")?; + + *height += U256::one(); + + Ok(()) + } + + /// Get serialized block by it's height + pub fn find_raw_by_height( + &self, + height: &U256, + ) -> Result>, Report> { + let chain_height = self.height.read(); + if height > &chain_height { + return Ok(None); + } + drop(chain_height); + + let mut height_serialized = [0u8; 32]; + height.to_big_endian(&mut height_serialized); + let mut dump = self + .blocks + .get(height_serialized) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight))?; + + if let Some(dump) = dump.take() { + return Ok(Some(dump.to_vec())); + } + Ok(None) + } + + /// Get serialized block by it's hash + pub fn find_raw_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report> { + let height = match self + .height_reference + .get(hash) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))? + { + None => { + return Ok(None); + } + Some(h) => U256::from_big_endian(&h.iter().copied().collect::>()), + }; + + let block = self + .find_raw_by_height(&height) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHashE))?; + + Ok(block) + } + + pub fn find_by_hash( + &self, + hash: &[u8; 32], + ) -> Result>, Report> { + let dump = self.find_raw_by_hash(hash)?; + + let deserialized = if let Some(data) = dump { + Some(Arc::new( + block::DerivativeBlock::parse(&data[1..]) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable(format!( + "Failed to deserialize latest main chain block with hash {:?}", + hash + ))?, + )) + } else { + None + }; + + Ok(deserialized) + } + + /// Get serialized last block of the chain + pub fn get_last_raw_block(&self) -> Result>, Report> { + let height = self.height.read(); + if height.is_zero() { + return Ok(None); + } + let last_block_index = *height - 1; + drop(height); + + self.find_raw_by_height(&last_block_index) + } + + /// Get deserialized latest block + pub fn get_last_block(&self) -> Result, Report> { + let dump = self.get_last_raw_block()?; + + let deserialized = if let Some(data) = dump { + Some( + block::DerivativeBlock::parse(&data[1..]) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable( + "Failed to deserialize latest main chain block".to_string(), + )?, + ) + } else { + None + }; + + Ok(deserialized) + } + + /// Get deserialized block by height + pub fn find_by_height( + &self, + height: &U256, + ) -> Result>, Report> { + let dump = self.find_raw_by_height(height)?; + + let deserialized = if let Some(data) = dump { + Some(Arc::new( + block::DerivativeBlock::parse(&data[1..]) + .change_context(BlockChainTreeError::Chain(ChainErrorKind::FindByHeight)) + .attach_printable(format!( + "Failed to deserialize deriv chain block with height {}", + height + ))?, + )) + } else { + None + }; + + Ok(deserialized) + } +} diff --git a/src/dump_headers.rs b/src/dump_headers.rs index 76e6a33..e3d9492 100644 --- a/src/dump_headers.rs +++ b/src/dump_headers.rs @@ -6,7 +6,7 @@ pub enum Headers { Transaction = 0, Token = 1, TransactionBlock = 2, - TokenBlock = 3, + DerivativeBlock = 3, SummarizeBlock = 4, GenesisBlock = 5, } @@ -17,7 +17,7 @@ impl Headers { 0 => Ok(Headers::Transaction), 1 => Ok(Headers::Token), 2 => Ok(Headers::TransactionBlock), - 3 => Ok(Headers::TokenBlock), + 3 => Ok(Headers::DerivativeBlock), 4 => Ok(Headers::SummarizeBlock), _ => Err(Report::new(DumpHeadersError::DumpHeadersError( DumpHeadersErrorKind::UknownHeader, diff --git a/src/errors.rs b/src/errors.rs index 1963ce6..1ffa56b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -73,7 +73,7 @@ root_errors![ BasicInfo(BasicInfoErrorKind), TransactionToken(TxTokenErrorKind), TransactionBlock(TxBlockErrorKind), - TokenBlock(TokenBlockErrorKind), + DerivativeBlock(DerivativeBlockErrorKind), SummarizeBlock(SummarizeBlockErrorKind), HeaderError(DumpHeadersErrorKind), NotImplemented(NotImplementedKind) @@ -128,7 +128,7 @@ sub_errors![ Dump: "failed to dump", Parse: "failed to parse" }, - TokenBlockErrorKind { + DerivativeBlockErrorKind { Dump: "failed to dump", Parse: "failed to parse" }, @@ -163,6 +163,7 @@ sub_errors![ Init: "failed to init the blockchain tree (with config)", InitWithoutConfig: "failed to init the blockchain tree (with config)", DumpPool: "failed to dump pool", + DumpDb: "failed to dump database", GetDerivChain: "failed to get the derivative chain", CreateDerivChain: "failed to create the derivative chain", CheckMainFolders: "failed to check and fix the main folders", @@ -173,6 +174,7 @@ sub_errors![ MoveSummaryDB: "failed to move summary database", NewTransaction: "failed to create new transaction", CreateMainChainBlock: "failed to create new block for the main chain", - WrongPow: "supplied pow does not satisfy requirements" + WrongPow: "supplied pow does not satisfy requirements", + SummarizeBlockWrongTransactionsAmount: "summarization block should not have transactions" } ]; diff --git a/src/lib.rs b/src/lib.rs index ed5037f..e68b378 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ #![allow(dead_code)] pub mod block; pub mod blockchaintree; +pub mod chain; pub mod dump_headers; pub mod errors; pub mod merkletree; +pub mod static_values; pub mod summary_db; pub mod tools; pub mod transaction; +pub mod txpool; +pub mod types; diff --git a/src/merkletree.rs b/src/merkletree.rs index 1f8ec86..64388a3 100644 --- a/src/merkletree.rs +++ b/src/merkletree.rs @@ -46,7 +46,7 @@ impl MerkleTree { } for left_index in (1..nodes_total - 1).step_by(2).rev() { - //let mut hasher = Sha256::new(); + let mut to_hash = [0u8; 32]; unsafe { for (index, (left, right)) in array_representation @@ -57,9 +57,8 @@ impl MerkleTree { { *to_hash.get_unchecked_mut(index) = *left & *right; } - //hasher.reset(); - - let hash = Sha256::digest(&to_hash); + + let hash = Sha256::digest(to_hash); *(array_representation.get_unchecked_mut((left_index - 1) / 2)) = hash.as_slice().try_into().unwrap_unchecked(); @@ -96,7 +95,7 @@ impl MerkleTree { let lsb_set = index & 1; let lsb_not_set = lsb_set ^ 1; - // let mut sibling_index = index; + index += lsb_set; index -= lsb_not_set; @@ -152,20 +151,14 @@ impl MerkleTree { unsafe { self.array_representation.get_unchecked(0) } } - // pub fn new() -> MerkleTree { - // todo!() - // } - - // pub fn add_objects(&mut self, input: &Vec<[u8; 32]>) -> bool { - // todo!() - // } + } #[cfg(test)] mod tests { use super::MerkleTree; use rand::Rng; - use std::time::{Duration, Instant}; + use std::time::Instant; #[test] fn merkle_tree_test() { diff --git a/src/static_values.rs b/src/static_values.rs new file mode 100644 index 0000000..3dd2934 --- /dev/null +++ b/src/static_values.rs @@ -0,0 +1,53 @@ +use lazy_static::lazy_static; +use primitive_types::U256; + +pub static BLOCKCHAIN_DIRECTORY: &str = "./BlockChainTree/"; + +pub static AMMOUNT_SUMMARY: &str = "./BlockChainTree/SUMMARY/"; +pub static OLD_AMMOUNT_SUMMARY: &str = "./BlockChainTree/SUMMARYOLD/"; + +pub static GAS_SUMMARY: &str = "./BlockChainTree/GASSUMMARY/"; +pub static OLD_GAS_SUMMARY: &str = "./BlockChainTree/GASSUMMARYOLD/"; + +pub static MAIN_CHAIN_DIRECTORY: &str = "./BlockChainTree/MAIN/"; + +pub static DERIVATIVE_CHAINS_DIRECTORY: &str = "./BlockChainTree/DERIVATIVES/"; +pub static CHAINS_FOLDER: &str = "CHAINS/"; + +pub static BLOCKS_FOLDER: &str = "BLOCKS/"; +pub static REFERENCES_FOLDER: &str = "REF/"; +pub static TRANSACTIONS_FOLDER: &str = "TRANSACTIONS/"; + +pub static CONFIG_FILE: &str = "Chain.config"; +pub static LOOKUP_TABLE_FILE: &str = "LookUpTable.dat"; +pub static TRANSACTIONS_POOL: &str = "TRXS_POOL.pool"; + +pub static BEGINNING_DIFFICULTY: [u8; 32] = [ + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +]; + +pub static MAX_DIFFICULTY: [u8; 32] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128, +]; + +pub static ROOT_PRIVATE_ADDRESS: [u8; 32] = [1u8; 32]; +pub static ROOT_PUBLIC_ADDRESS: [u8; 33] = [ + 3, 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, 52, 96, + 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, +]; + +pub static INCEPTION_TIMESTAMP: u64 = 1597924800; + +pub static BLOCKS_PER_EPOCH: usize = 4; + +pub static TIME_PER_BLOCK: u64 = 4; + +lazy_static! { + pub static ref COIN_FRACTIONS: U256 = U256::from_dec_str("1000000000000000000").unwrap(); + pub static ref INITIAL_FEE: U256 = U256::from_dec_str("25000000000000000").unwrap(); // 100_000_000//4 + pub static ref FEE_STEP: U256 = U256::from_dec_str("625000000000").unwrap(); // 100_000_000//255 + pub static ref MAIN_CHAIN_PAYMENT: U256 = *INITIAL_FEE; + pub static ref COINS_PER_CYCLE: U256 = (*MAIN_CHAIN_PAYMENT*2000usize*BLOCKS_PER_EPOCH) + *COIN_FRACTIONS*10000usize; +} diff --git a/src/summary_db.rs b/src/summary_db.rs index 3f2a91a..2c3c09e 100644 --- a/src/summary_db.rs +++ b/src/summary_db.rs @@ -1,6 +1,7 @@ -use error_stack::{IntoReport, Report, Result, ResultExt}; +use error_stack::{Report, Result, ResultExt}; use num_bigint::BigUint; use num_traits::Zero; +use primitive_types::U256; use sled::Db; use crate::{ @@ -44,7 +45,6 @@ impl SummaryDB { self.db .flush_async() .await - .into_report() .change_context(BlockChainTreeError::Chain(ChainErrorKind::DumpConfig)) .attach_printable("failed to flush db")?; @@ -57,7 +57,7 @@ impl SummaryDB { pub async fn decrease_funds( &self, addr: &[u8; 33], - funds: &BigUint, + funds: &U256, ) -> Result<(), BlockChainTreeError> { let result = self.db.get(addr); match result { @@ -69,7 +69,7 @@ impl SummaryDB { std::str::from_utf8(addr).unwrap() ))), Ok(Some(prev)) => { - let res = tools::load_biguint(&prev).change_context( + let res = tools::load_u256(&prev).change_context( BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DecreaseFunds), )?; @@ -80,16 +80,15 @@ impl SummaryDB { )) .attach_printable("insufficient balance")); } - previous -= funds; + previous -= *funds; - let mut dump: Vec = Vec::with_capacity(tools::bigint_size(&previous)); - tools::dump_biguint(&previous, &mut dump).change_context( + let mut dump: Vec = Vec::with_capacity(tools::u256_size(&previous)); + tools::dump_u256(&previous, &mut dump).change_context( BlockChainTreeError::BlockChainTree(BCTreeErrorKind::DecreaseFunds), )?; self.db .insert(addr, dump) - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::DecreaseFunds, )) @@ -98,7 +97,6 @@ impl SummaryDB { self.db .flush_async() .await - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::AddFunds, )) @@ -124,7 +122,7 @@ impl SummaryDB { pub async fn add_funds( &self, addr: &[u8; 33], - funds: &BigUint, + funds: &U256, ) -> Result<(), BlockChainTreeError> { if funds.is_zero() { return Ok(()); @@ -132,14 +130,13 @@ impl SummaryDB { let result = self.db.get(addr); match result { Ok(None) => { - let mut dump: Vec = Vec::with_capacity(tools::bigint_size(funds)); - tools::dump_biguint(funds, &mut dump).change_context( + let mut dump: Vec = Vec::with_capacity(tools::u256_size(funds)); + tools::dump_u256(funds, &mut dump).change_context( BlockChainTreeError::BlockChainTree(BCTreeErrorKind::AddFunds), )?; self.db .insert(addr, dump) - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::AddFunds, )) @@ -151,7 +148,6 @@ impl SummaryDB { self.db .flush_async() .await - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::AddFunds, )) @@ -163,21 +159,20 @@ impl SummaryDB { Ok(()) } Ok(Some(prev)) => { - let res = tools::load_biguint(&prev).change_context( + let res = tools::load_u256(&prev).change_context( BlockChainTreeError::BlockChainTree(BCTreeErrorKind::AddFunds), )?; let mut previous = res.0; - previous += funds; + previous += *funds; - let mut dump: Vec = Vec::with_capacity(tools::bigint_size(&previous)); - tools::dump_biguint(&previous, &mut dump).change_context( + let mut dump: Vec = Vec::with_capacity(tools::u256_size(&previous)); + tools::dump_u256(&previous, &mut dump).change_context( BlockChainTreeError::BlockChainTree(BCTreeErrorKind::AddFunds), )?; self.db .insert(addr, dump) - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::AddFunds, )) @@ -189,7 +184,6 @@ impl SummaryDB { self.db .flush_async() .await - .into_report() .change_context(BlockChainTreeError::BlockChainTree( BCTreeErrorKind::AddFunds, )) diff --git a/src/tools.rs b/src/tools.rs index 78bf97a..d91f98f 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -1,12 +1,15 @@ use crate::errors::*; -use error_stack::{IntoReport, Report, Result, ResultExt}; +use crate::static_values::{FEE_STEP, TIME_PER_BLOCK}; +use crate::types::Hash; +use error_stack::{Report, Result, ResultExt}; use num_bigint::BigUint; +use primitive_types::U256; use sha2::{Digest, Sha256}; +use std::cmp::Ordering; use std::convert::TryInto; use std::fs::File; use std::io::Read; use std::io::Write; -use std::mem::transmute; use std::path::Path; use std::{fs, io}; @@ -24,6 +27,68 @@ pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result< Ok(()) } +pub fn dump_u256(number: &U256, buffer: &mut Vec) -> Result<(), ToolsError> { + buffer.push(0); + let ind = buffer.len() - 1; + + let mut found_non_null = false; + let mut counter: u8 = 0; + + for num in number.0.iter().rev() { + let bytes = num.to_be().to_ne_bytes(); + for byte in bytes { + if found_non_null { + buffer.push(byte); + counter += 1; + } else if byte != 0 { + buffer.push(byte); + counter += 1; + found_non_null = true; + } + } + } + + unsafe { *buffer.get_unchecked_mut(ind) = counter }; + + Ok(()) +} + +pub fn load_u256(data: &[u8]) -> Result<(U256, usize), ToolsError> { + let amount_of_bytes: usize = data[0] as usize; + + if amount_of_bytes > 32 { + return Err(Report::new(ToolsError::Biguint(BiguintErrorKind::Dump))); + } + + if data.len() < amount_of_bytes { + return Err( + Report::new(ToolsError::Biguint(BiguintErrorKind::Load)).attach_printable(format!( + "data = {} // bytes = {}", + data.len(), + amount_of_bytes + )), + ); + } + + Ok(( + U256::from_big_endian(&data[1..1 + amount_of_bytes]), + amount_of_bytes, + )) +} + +pub fn u256_size(number: &U256) -> usize { + let bits_size: usize = number.bits(); + if bits_size == 0 { + return 2; + } + let mut amount_byte_size: usize = bits_size / 8; + if number.bits() % 8 != 0 { + amount_byte_size += 1; + } + + amount_byte_size + 1 +} + pub fn dump_biguint(number: &BigUint, buffer: &mut Vec) -> Result<(), ToolsError> { let number_bytes: Vec = number.to_bytes_le(); @@ -80,22 +145,18 @@ pub fn hash(data: &[u8]) -> [u8; 32] { pub fn compress_to_file(output_file: String, data: &[u8]) -> Result<(), ToolsError> { let path = Path::new(&output_file); - let target = File::create(path) - .into_report() - .change_context(ToolsError::Zstd(ZstdErrorKind::CompressingFile))?; + let target = + File::create(path).change_context(ToolsError::Zstd(ZstdErrorKind::CompressingFile))?; let mut encoder = zstd::Encoder::new(target, 1) - .into_report() .change_context(ToolsError::Zstd(ZstdErrorKind::CompressingFile))?; encoder .write_all(data) - .into_report() .change_context(ToolsError::Zstd(ZstdErrorKind::CompressingFile))?; encoder .finish() - .into_report() .change_context(ToolsError::Zstd(ZstdErrorKind::CompressingFile))?; Ok(()) @@ -106,46 +167,176 @@ pub fn decompress_from_file(filename: String) -> Result, ToolsError> { let mut decoded_data: Vec = Vec::new(); let file = File::open(path) - .into_report() .attach_printable("Error opening file") .change_context(ToolsError::Zstd(ZstdErrorKind::DecompressingFile))?; let mut decoder = zstd::Decoder::new(file) - .into_report() .attach_printable("Error creating decoder") .change_context(ToolsError::Zstd(ZstdErrorKind::DecompressingFile))?; decoder .read_to_end(&mut decoded_data) - .into_report() .attach_printable("Error reading file") .change_context(ToolsError::Zstd(ZstdErrorKind::DecompressingFile))?; Ok(decoded_data) } -pub fn check_pow(prev_hash: &[u8; 32], difficulty: &[u8; 32], pow: &[u8]) -> bool { +#[inline] +pub fn count_leading_zeros(data: &[u8]) -> u32 { + let mut to_return = 0u32; + for byte in data { + let leading_zeros = byte.leading_zeros(); + to_return += leading_zeros; + if leading_zeros < 8 { + break; + } + } + to_return +} + +pub fn check_pow(hash: &[u8; 32], difficulty: &[u8; 32], pow: &[u8]) -> bool { let mut hasher = Sha256::new(); - hasher.update(prev_hash); + hasher.update(hash); hasher.update(pow); - let result: [u8; 32] = unsafe { hasher.finalize().as_slice().try_into().unwrap_unchecked() }; - let result: [u64; 4] = unsafe { transmute(result) }; + let result: [u8; 32] = hasher.finalize().into(); - let difficulty: &[u64; 4] = unsafe { transmute(difficulty) }; - - //println!("difficulty: {:?}", difficulty); + if count_leading_zeros(difficulty) <= count_leading_zeros(&result) { + return true; + } + false +} - for (r, d) in result.iter().zip(difficulty) { - match r.cmp(d) { - std::cmp::Ordering::Less => { - return true; +pub fn recalculate_difficulty(prev_timestamp: u64, timestamp: u64, prev_difficulty: &mut Hash) { + let mut non_zero_index: usize = 0; + for (index, val) in prev_difficulty.iter().enumerate() { + if !0.eq(val) { + non_zero_index = index; + break; + }; + } + match (timestamp - prev_timestamp).cmp(&TIME_PER_BLOCK) { + Ordering::Less => { + let val = unsafe { prev_difficulty.get_unchecked_mut(non_zero_index) }; + *val >>= 1; + } + Ordering::Greater => { + let mut val = unsafe { prev_difficulty.get_unchecked_mut(non_zero_index) }; + if non_zero_index == 0 && *val == 0x7f { + return; } - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => { - return false; + if *val == 0xFF { + val = unsafe { prev_difficulty.get_unchecked_mut(non_zero_index - 1) }; } + *val <<= 1; + *val += 1; } + Ordering::Equal => (), + } +} + +pub fn recalculate_fee(current_difficulty: &Hash) -> U256 { + let leading_zeros = count_leading_zeros(current_difficulty); + + *FEE_STEP * leading_zeros +} + +#[cfg(test)] +mod tests { + + use primitive_types::U256; + + use super::{dump_u256, load_u256, recalculate_difficulty}; + + #[test] + fn dump_load_u256() { + let mut dump: Vec = Vec::new(); + + println!( + "{:?}", + U256::from_dec_str("10000000000000000000001000000001") + ); + + dump_u256( + &U256::from_dec_str("10000000000000000000001000000001").unwrap(), + &mut dump, + ) + .unwrap(); + + let num = load_u256(&dump).unwrap(); + + assert_eq!( + U256::from_dec_str("10000000000000000000001000000001").unwrap(), + num.0 + ); } - true + #[test] + fn recalculate_difficulty_test() { + let mut difficulty: [u8; 32] = [ + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + + recalculate_difficulty(10, 20, &mut difficulty); + assert_eq!(difficulty[0], 0b00111111); + + difficulty[0] = 0; + + recalculate_difficulty(10, 20, &mut difficulty); + assert_eq!( + difficulty, + [ + 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ] + ); + + let mut difficulty: [u8; 32] = [ + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + recalculate_difficulty(10, 700, &mut difficulty); + assert_eq!( + difficulty, + [ + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ] + ); + + let mut difficulty: [u8; 32] = [ + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + recalculate_difficulty(10, 700, &mut difficulty); + assert_eq!( + difficulty, + [ + 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ] + ); + + let mut difficulty: [u8; 32] = [ + 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + recalculate_difficulty(10, 700, &mut difficulty); + assert_eq!( + difficulty, + [ + 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ] + ); + } } diff --git a/src/transaction.rs b/src/transaction.rs index 7f79737..aeeb25b 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,6 +1,6 @@ use crate::errors::*; use crate::tools; -use num_bigint::BigUint; +use primitive_types::U256; use sha2::{Digest, Sha256}; use std::cmp::Ordering; use std::convert::TryInto; @@ -12,7 +12,7 @@ use secp256k1::PublicKey; use secp256k1::{Message, Secp256k1, SecretKey}; use std::mem::transmute; -use error_stack::{IntoReport, Report, Result, ResultExt}; +use error_stack::{Report, Result, ResultExt}; pub type TransactionableItem = Box; @@ -42,23 +42,7 @@ impl Ord for TransactionableItem { impl PartialOrd for TransactionableItem { fn partial_cmp(&self, other: &Self) -> Option { - Some(match self.get_timestamp().cmp(&other.get_timestamp()) { - Ordering::Less => Ordering::Greater, - Ordering::Equal => { - let tr_hash: [u64; 4] = unsafe { transmute(self.hash()) }; - let other_hash: [u64; 4] = unsafe { transmute(other.hash()) }; - - for (left, right) in tr_hash.iter().zip(other_hash.iter()) { - match left.cmp(right) { - Ordering::Less => return Some(Ordering::Greater), - Ordering::Equal => {} - Ordering::Greater => return Some(Ordering::Less), - } - } - Ordering::Equal - } - Ordering::Greater => Ordering::Less, - }) + Some(self.cmp(other)) } } @@ -69,15 +53,15 @@ impl PartialEq for TransactionableItem { } pub trait Transactionable: Send + Sync { - fn hash(&self) -> [u8; 32]; fn hash_without_signature(&self) -> [u8; 32]; fn verify(&self) -> Result; fn dump(&self) -> Result, TransactionError>; + fn hash(&self) -> [u8; 32]; fn get_dump_size(&self) -> usize; - fn parse(data: &[u8], size: u64) -> Result + fn parse(data: &[u8]) -> Result where Self: Sized; @@ -85,17 +69,20 @@ pub trait Transactionable: Send + Sync { fn get_receiver(&self) -> &[u8; 33]; fn get_timestamp(&self) -> u64; fn get_signature(&self) -> &[u8; 64]; - fn get_amount(&self) -> Option; + fn get_amount(&self) -> Option; + fn get_data(&self) -> Option<&[u8]>; } #[derive(Debug, Clone)] pub struct Transaction { - hash: [u8; 32], sender: [u8; 33], receiver: [u8; 33], timestamp: u64, signature: [u8; 64], - amount: BigUint, + amount: U256, + gas_amount: U256, + data: Option>, + hash: [u8; 32], } impl Transaction { @@ -103,15 +90,23 @@ impl Transaction { sender: &[u8; 33], receiver: &[u8; 33], timestamp: u64, - amount: &BigUint, + amount: &U256, + gas_amount: &U256, + data: Option<&[u8]>, private_key: &[u8; 32], ) -> [u8; 64] { let mut hasher = Sha256::new(); - let amount_as_bytes = amount.to_bytes_be(); - let calculated_size: usize = 33 + 33 + 8 + amount_as_bytes.len(); + let calculated_size: usize = 1 + + 33 + + 33 + + 8 + + tools::u256_size(amount) + + tools::u256_size(gas_amount) + + data.map_or(0, |data| data.len()); let mut concatenated_input: Vec = Vec::with_capacity(calculated_size); + concatenated_input.push(Headers::Transaction as u8); for byte in sender.iter() { concatenated_input.push(*byte); } @@ -121,13 +116,21 @@ impl Transaction { for byte in timestamp.to_be_bytes().iter() { concatenated_input.push(*byte); } - for byte in amount_as_bytes.iter() { - concatenated_input.push(*byte); + tools::dump_u256(amount, &mut concatenated_input) + .attach_printable("Error to dump amount") + .change_context(TransactionError::Tx(TxErrorKind::Dump)) + .unwrap(); + tools::dump_u256(gas_amount, &mut concatenated_input) + .attach_printable("Error to dump gas amount") + .change_context(TransactionError::Tx(TxErrorKind::Dump)) + .unwrap(); + if let Some(data) = data { + concatenated_input.extend(data.iter()); } hasher.update(concatenated_input); let result: [u8; 32] = hasher.finalize().as_slice().try_into().unwrap(); - let message = unsafe { Message::from_slice(&result).unwrap_unchecked() }; + let message = unsafe { Message::from_digest_slice(&result).unwrap_unchecked() }; let secret_key = unsafe { SecretKey::from_slice(private_key).unwrap_unchecked() }; @@ -138,92 +141,83 @@ impl Transaction { signature.serialize_compact() } - pub fn generate_hash( - sender: &[u8; 33], - receiver: &[u8; 33], - timestamp: u64, - signature: &[u8; 64], - amount: &BigUint, - ) -> [u8; 32] { - let mut hasher = Sha256::new(); - - let amount_as_bytes = amount.to_bytes_be(); - let calculated_size: usize = 33 + 33 + 8 + amount_as_bytes.len(); - - let mut concatenated_input: Vec = Vec::with_capacity(calculated_size); - for byte in sender.iter() { - concatenated_input.push(*byte); - } - for byte in receiver.iter() { - concatenated_input.push(*byte); - } - for byte in signature.iter() { - concatenated_input.push(*byte); - } - for byte in timestamp.to_be_bytes().iter() { - concatenated_input.push(*byte); - } - for byte in amount_as_bytes.iter() { - concatenated_input.push(*byte); - } - - hasher.update(concatenated_input); - hasher.finalize().as_slice().try_into().unwrap() - } - pub fn new( sender: [u8; 33], receiver: [u8; 33], timestamp: u64, - amount: BigUint, + amount: U256, + gas_amount: U256, private_key: [u8; 32], + data: Option>, ) -> Transaction { - let signature = - Transaction::generate_signature(&sender, &receiver, timestamp, &amount, &private_key); - Transaction { - hash: Transaction::generate_hash(&sender, &receiver, timestamp, &signature, &amount), + let signature = Transaction::generate_signature( + &sender, + &receiver, + timestamp, + &amount, + &gas_amount, + data.as_deref(), + &private_key, + ); + let mut tr = Transaction { sender, receiver, timestamp, signature, amount, - } + gas_amount, + data, + hash: [0; 32], + }; + tr.hash = tools::hash(&tr.dump().unwrap()); + + tr } pub fn new_signed( - hash: [u8; 32], + //hash: [u8; 32], sender: [u8; 33], receiver: [u8; 33], timestamp: u64, - amount: BigUint, + amount: U256, + gas_amount: U256, + data: Option>, signature: [u8; 64], ) -> Transaction { - Transaction { - hash, + let mut tr = Transaction { sender, receiver, timestamp, signature, amount, - } + gas_amount, + data, + hash: [0; 32], + }; + tr.hash = tools::hash(&tr.dump().unwrap()); + + tr } - pub fn get_amount(&self) -> &BigUint { + pub fn get_amount(&self) -> &U256 { &self.amount } } impl Transactionable for Transaction { - fn hash(&self) -> [u8; 32] { - self.hash - } fn hash_without_signature(&self) -> [u8; 32] { let mut hasher = Sha256::new(); - let amount_as_bytes = self.amount.to_bytes_be(); - let calculated_size: usize = 33 + 33 + 8 + amount_as_bytes.len(); + let calculated_size: usize = 1 + + 33 + + 33 + + 8 + + tools::u256_size(&self.amount) + + tools::u256_size(&self.gas_amount) + + self.data.as_ref().map_or(0, |data| data.len()); let mut concatenated_input: Vec = Vec::with_capacity(calculated_size); + concatenated_input.push(Headers::Transaction as u8); for byte in self.sender.iter() { concatenated_input.push(*byte); } @@ -233,14 +227,22 @@ impl Transactionable for Transaction { for byte in self.timestamp.to_be_bytes().iter() { concatenated_input.push(*byte); } - for byte in amount_as_bytes.iter() { - concatenated_input.push(*byte); + tools::dump_u256(&self.amount, &mut concatenated_input) + .attach_printable("Error to dump amount") + .change_context(TransactionError::Tx(TxErrorKind::Dump)) + .unwrap(); + + tools::dump_u256(&self.gas_amount, &mut concatenated_input) + .attach_printable("Error to dump gas amount") + .change_context(TransactionError::Tx(TxErrorKind::Dump)) + .unwrap(); + + if let Some(data) = self.data.as_ref() { + concatenated_input.extend(data.iter()); } hasher.update(concatenated_input); - let result: [u8; 32] = hasher.finalize().as_slice().try_into().unwrap(); - - result + unsafe { hasher.finalize().as_slice().try_into().unwrap_unchecked() } } fn verify(&self) -> Result { @@ -248,20 +250,17 @@ impl Transactionable for Transaction { // load sender let sender = PublicKey::from_slice(&self.sender) - .into_report() .change_context(TransactionError::Tx(TxErrorKind::Verify))?; // creating verifier let verifier = Secp256k1::verification_only(); // load message - let message = Message::from_slice(&signed_data_hash) - .into_report() + let message = Message::from_digest_slice(&signed_data_hash) .change_context(TransactionError::Tx(TxErrorKind::Verify))?; // load signature let signature = Signature::from_compact(&self.signature) - .into_report() .change_context(TransactionError::Tx(TxErrorKind::Verify))?; // verifying hashed data with public key @@ -274,8 +273,6 @@ impl Transactionable for Transaction { } fn dump(&self) -> Result, TransactionError> { - let timestamp_as_bytes: [u8; 8] = self.timestamp.to_be_bytes(); - let calculated_size: usize = self.get_dump_size(); let mut transaction_dump: Vec = Vec::with_capacity(calculated_size); @@ -283,11 +280,6 @@ impl Transactionable for Transaction { // header transaction_dump.push(Headers::Transaction as u8); - // hash - for byte in self.hash.iter() { - transaction_dump.push(*byte); - } - // sender for byte in self.sender.iter() { transaction_dump.push(*byte); @@ -299,7 +291,7 @@ impl Transactionable for Transaction { } // timestamp - transaction_dump.extend(timestamp_as_bytes.iter()); + transaction_dump.extend(self.timestamp.to_be_bytes().iter()); // signature for byte in self.signature.iter() { @@ -307,28 +299,39 @@ impl Transactionable for Transaction { } // amount - tools::dump_biguint(&self.amount, &mut transaction_dump) + tools::dump_u256(&self.amount, &mut transaction_dump) + .change_context(TransactionError::Tx(TxErrorKind::Dump))?; + + // gas amount + tools::dump_u256(&self.gas_amount, &mut transaction_dump) .change_context(TransactionError::Tx(TxErrorKind::Dump))?; + // data + if let Some(data) = self.data.as_ref() { + transaction_dump.extend(data.iter()); + } + Ok(transaction_dump) } fn get_dump_size(&self) -> usize { - 1 + 32 + 33 + 33 + 8 + 64 + tools::bigint_size(&self.amount) + 1 + 33 + + 33 + + 8 + + 64 + + tools::u256_size(&self.amount) + + tools::u256_size(&self.gas_amount) + + self.data.as_ref().map_or(0, |data| data.len()) } - fn parse(data: &[u8], size: u64) -> Result { + fn parse(data: &[u8]) -> Result { let mut index: usize = 0; - if data.len() <= 170 { + if data.len() < 139 { return Err(Report::new(TransactionError::Tx(TxErrorKind::Parse)) - .attach_printable("Data length <= 170")); + .attach_printable("Data length < 139")); } - // parsing hash - let hash: [u8; 32] = unsafe { data[index..index + 32].try_into().unwrap_unchecked() }; - index += 32; - // parsing sender address let sender: [u8; 33] = unsafe { data[index..index + 33].try_into().unwrap_unchecked() }; index += 33; @@ -346,18 +349,35 @@ impl Transactionable for Transaction { index += 64; // parsing amount - let (amount, idx) = tools::load_biguint(&data[index..]) + let (amount, idx) = tools::load_u256(&data[index..]) .attach_printable("Couldn't parse amount") .change_context(TransactionError::Tx(TxErrorKind::Parse))?; - index += idx; - if index != size as usize { + index += idx + 1; + + // parsing amount + let (gas_amount, idx) = tools::load_u256(&data[index..]) + .attach_printable("Couldn't parse gas amount") + .change_context(TransactionError::Tx(TxErrorKind::Parse))?; + + index += idx + 1; + + let tx_data = if index == data.len() { + None + } else { + let mut new_data = Vec::::with_capacity(data.len() - index); + new_data.extend(data[index..].iter()); + index += new_data.len(); + Some(new_data) + }; + + if index != data.len() { return Err(Report::new(TransactionError::Tx(TxErrorKind::Parse)) .attach_printable("Index != Tx size")); } Ok(Transaction::new_signed( - hash, sender, receiver, timestamp, amount, signature, + sender, receiver, timestamp, amount, gas_amount, tx_data, signature, )) } @@ -376,7 +396,15 @@ impl Transactionable for Transaction { fn get_signature(&self) -> &[u8; 64] { &self.signature } - fn get_amount(&self) -> Option { - Some(self.amount.clone()) + fn get_amount(&self) -> Option { + Some(self.amount) + } + + fn get_data(&self) -> Option<&[u8]> { + self.data.as_deref() + } + + fn hash(&self) -> [u8; 32] { + self.hash } } diff --git a/src/txpool.rs b/src/txpool.rs new file mode 100644 index 0000000..2c8b854 --- /dev/null +++ b/src/txpool.rs @@ -0,0 +1,58 @@ +use std::collections::binary_heap::Iter; +use std::collections::{BinaryHeap, HashSet}; +use std::sync::Arc; + +use tokio::sync::RwLock; + +use crate::transaction::TransactionableItem; + +pub type SharedTxPool = Arc>; + +#[derive(Default)] +pub struct TxPool { + pool: BinaryHeap, + hashes: HashSet<[u8; 32]>, +} + +impl TxPool { + pub fn new() -> TxPool { + TxPool::default() + } + pub fn with_capacity(capacity: usize) -> TxPool { + TxPool { + pool: BinaryHeap::with_capacity(capacity), + hashes: HashSet::with_capacity(capacity), + } + } + + pub fn push(&mut self, transaction: TransactionableItem) -> bool { + if !self.hashes.insert(transaction.hash()) { + return false; + } + self.pool.push(transaction); + true + } + + pub fn len(&self) -> usize { + self.hashes.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn transactions_iter(&self) -> Iter<'_, TransactionableItem> { + self.pool.iter() + } + + pub fn pop(&mut self) -> Option<([u8; 32], TransactionableItem)> { + let tr = self.pool.pop()?; + let hash = tr.hash(); + self.hashes.remove(&hash); + Some((hash, tr)) + } + + pub fn transaction_exists(&self, hash: &[u8; 32]) -> bool { + self.hashes.contains(hash) + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..159a67f --- /dev/null +++ b/src/types.rs @@ -0,0 +1,4 @@ +pub type TransactionData = Vec; +pub type Address = [u8; 33]; +pub type Hash = [u8; 32]; +pub type Signature = [u8; 64]; diff --git a/tests/block_test.rs b/tests/block_test.rs new file mode 100644 index 0000000..aabc2bf --- /dev/null +++ b/tests/block_test.rs @@ -0,0 +1,229 @@ +use std::sync::Arc; + +use blockchaintree::block::{self, Block, DerivativeBlock}; +use primitive_types::U256; + +#[test] +fn dump_parse_basic_info() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: [5; 32], + height: U256::from_dec_str("6378216378216387213672813821736").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + + let mut buffer: Vec = Vec::new(); + + basic_data.dump(&mut buffer).unwrap(); + + let basic_data_loaded = block::BasicInfo::parse(&buffer).unwrap(); + + assert_eq!(basic_data.timestamp, basic_data_loaded.timestamp); + assert_eq!(basic_data.pow, basic_data_loaded.pow); + assert_eq!(basic_data.previous_hash, basic_data_loaded.previous_hash); + assert_eq!(basic_data.height, basic_data_loaded.height); + assert_eq!(basic_data.difficulty, basic_data_loaded.difficulty); + assert_eq!(basic_data.founder, basic_data_loaded.founder); + + println!("{:?}", basic_data_loaded) +} + +#[test] +fn dump_parse_block() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: [5; 32], + height: U256::from_dec_str("6378216378216387213672813821736").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let block = block::TransactionBlock::new( + U256::from_dec_str("9089878746387246532").unwrap(), + basic_data, + [5; 32], + vec![[1; 32], [2; 32], [3; 32]], + ); + + let dump = block.dump().unwrap(); + + let block_loaded = block::TransactionBlock::parse(&dump[1..]).unwrap(); + + assert_eq!(block.merkle_tree_root, block_loaded.merkle_tree_root); + assert_eq!(block.fee, block_loaded.fee); + assert_eq!(block.transactions, block_loaded.transactions); + + assert_eq!( + block.default_info.timestamp, + block_loaded.default_info.timestamp + ); + assert_eq!(block.default_info.pow, block_loaded.default_info.pow); + assert_eq!( + block.default_info.previous_hash, + block_loaded.default_info.previous_hash + ); + assert_eq!(block.default_info.height, block_loaded.default_info.height); + assert_eq!( + block.default_info.difficulty, + block_loaded.default_info.difficulty + ); + assert_eq!( + block.default_info.founder, + block_loaded.default_info.founder + ); + + println!("{:?}", block_loaded); +} + +#[test] +fn dump_parse_summarize_block() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: [5; 32], + height: U256::from_dec_str("6378216378216387213672813821736").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let block = block::SummarizeBlock { + default_info: basic_data, + merkle_tree_root: [5; 32], + }; + + let dump = block.dump().unwrap(); + + let block_loaded = block::SummarizeBlock::parse(&dump[1..]).unwrap(); + + assert_eq!(block.merkle_tree_root, block_loaded.merkle_tree_root); + + assert_eq!( + block.default_info.timestamp, + block_loaded.default_info.timestamp + ); + assert_eq!(block.default_info.pow, block_loaded.default_info.pow); + assert_eq!( + block.default_info.previous_hash, + block_loaded.default_info.previous_hash + ); + assert_eq!(block.default_info.height, block_loaded.default_info.height); + assert_eq!( + block.default_info.difficulty, + block_loaded.default_info.difficulty + ); + assert_eq!( + block.default_info.founder, + block_loaded.default_info.founder + ); + + println!("{:?}", block_loaded); +} + +#[test] +fn validate_block_test() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: [5; 32], + height: U256::from_dec_str("1").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let prev_block = block::TransactionBlock::new( + U256::from_dec_str("9089878746387246532").unwrap(), + basic_data, + [5; 32], + vec![[1; 32], [2; 32], [3; 32]], + ); + + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: prev_block.hash().unwrap(), + height: U256::from_dec_str("2").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let block = block::TransactionBlock::new( + U256::from_dec_str("9089878746387246532").unwrap(), + basic_data, + [5; 32], + vec![[1; 32], [2; 32], [3; 32]], + ); + + assert!(!block.validate(Some(Arc::new(prev_block))).unwrap()); +} + +#[test] +fn dump_parse_derivative_block() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: unsafe { [0; 32].try_into().unwrap_unchecked() }, + height: U256::from_dec_str("2").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let derivative_block = DerivativeBlock { + default_info: basic_data, + //payment_transaction, + }; + let dumped_block = derivative_block.dump().unwrap(); + let parsed_block = DerivativeBlock::parse(&dumped_block[1..]).unwrap(); + + assert_eq!( + derivative_block.default_info.timestamp, + parsed_block.default_info.timestamp + ); + assert_eq!( + derivative_block.default_info.pow, + parsed_block.default_info.pow + ); + assert_eq!( + derivative_block.default_info.previous_hash, + parsed_block.default_info.previous_hash + ); + assert_eq!( + derivative_block.default_info.height, + parsed_block.default_info.height + ); + assert_eq!( + derivative_block.default_info.difficulty, + parsed_block.default_info.difficulty + ); + assert_eq!( + derivative_block.default_info.founder, + parsed_block.default_info.founder + ); +} + +#[test] +fn validate_derivative_block() { + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: unsafe { [0; 32].try_into().unwrap_unchecked() }, + height: U256::from_dec_str("2").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let prev_block = DerivativeBlock { + default_info: basic_data, + }; + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: unsafe { [0; 32].try_into().unwrap_unchecked() }, + height: U256::from_dec_str("2").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let derivative_block = DerivativeBlock { + default_info: basic_data, + }; + + assert!(!derivative_block + .validate(Some(Arc::new(prev_block))) + .unwrap()); +} diff --git a/tests/blockchaintree_test.rs b/tests/blockchaintree_test.rs index a83b33b..13db1ba 100644 --- a/tests/blockchaintree_test.rs +++ b/tests/blockchaintree_test.rs @@ -1,196 +1,26 @@ -use std::str::FromStr; - -use blockchaintree::block::{self, BasicInfo, GenesisBlock, MainChainBlock, TransactionBlock}; -use blockchaintree::blockchaintree::{BlockChainTree, INCEPTION_TIMESTAMP, ROOT_PUBLIC_ADDRESS}; -use blockchaintree::tools::{self, check_pow}; -use blockchaintree::{self, blockchaintree::ROOT_PRIVATE_ADDRESS, transaction::Transactionable}; -use num_bigint::{BigUint, ToBigUint}; -use secp256k1::{PublicKey, Secp256k1, SecretKey}; - -static SENDER: &[u8; 33] = b"123456789012345678901234567890123"; -static RECIEVER: &[u8; 33] = b"123456689012345678901234567890123"; -//static SIGNATURE: &[u8; 64] = b"1234567890123456789012345678901234567890123456789012345678901234"; -static PREV_HASH: &[u8; 32] = b"12345678901234567890123456789012"; +use blockchaintree::blockchaintree::BlockChainTree; +use primitive_types::U256; #[tokio::test] -async fn chain_test() { - let blockchain = blockchaintree::blockchaintree::BlockChainTree::without_config().unwrap(); - - let default_info = BasicInfo::new( - 500, - vec![3, 232], - [0u8; 32], - //[1u8; 32], - 0, - [5u8; 32], - *SENDER, - ); - let tr = blockchaintree::transaction::Transaction::new( - *SENDER, - *RECIEVER, - 121212, - 2222222288u64.to_biguint().unwrap(), - *PREV_HASH, - ); - - let block = block::TokenBlock::new(default_info.clone(), String::new(), tr.clone()); - - let derivative_chain = - if let Some(chain) = blockchain.get_derivative_chain(SENDER).await.unwrap() { - chain - } else { - blockchain - .create_derivative_chain(SENDER, PREV_HASH, 0) - .await - .unwrap() - } - .clone(); +async fn test_amounts() { + let tree = BlockChainTree::new().unwrap(); - derivative_chain - .write() - .await - .add_block(&block) - .await + let address_a = [0; 33]; + let address_b = [1; 33]; + tree.add_amount(&address_a, U256::from_dec_str("10000000000").unwrap()) .unwrap(); + let amount = tree.get_amount(&address_a).unwrap(); + assert_eq!(amount, U256::from_dec_str("10000000000").unwrap()); - let block_db = derivative_chain - .read() - .await - .find_by_height(0) - .unwrap() + tree.send_amount(&address_a, &address_b, U256::from_dec_str("100").unwrap()) .unwrap(); - assert_eq!(block_db.payment_transaction.get_sender(), SENDER); - - let chain = blockchain.get_main_chain(); - let block = TransactionBlock::new( - vec![tr.hash()], - 50.to_biguint().unwrap(), - default_info, - [0u8; 32], - ); - chain.add_block_raw(&block).await.unwrap(); - - chain.add_transaction_raw(&tr).await.unwrap(); - - let loaded_transaction = chain.find_transaction(&tr.hash()).await.unwrap().unwrap(); - assert_eq!(loaded_transaction.get_sender(), SENDER); -} - -#[test] -fn generate_public_root_key() { - let secp = Secp256k1::new(); - let secret_key = SecretKey::from_slice(&ROOT_PRIVATE_ADDRESS).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &secret_key); - - println!("{:?}", public_key.serialize()); -} - -#[tokio::test] -async fn mine_main_chain() { - let blockchain = match BlockChainTree::with_config() { - Err(e) => { - println!("Failed to load blockchain with config {:?}", e.to_string()); - //info!("Trying to load blockchain without config"); - BlockChainTree::without_config().unwrap() - } - Ok(tree) => tree, - }; - - let chain = blockchain.get_main_chain(); - - println!("Difficulty: {:?}", chain.get_difficulty().await); - - let res = blockchain - .emit_main_chain_block(&[0], *SENDER, INCEPTION_TIMESTAMP + 10) - .await - .unwrap(); - + let amount_a = tree.get_amount(&address_a).unwrap(); + let amount_b = tree.get_amount(&address_b).unwrap(); + println!("{:?}", amount_a); + println!("{:?}", amount_b); assert_eq!( - chain - .get_last_block() - .await - .unwrap() - .unwrap() - .hash() - .unwrap(), - res.hash().unwrap() - ); - - assert_ne!( - blockchain.get_funds(SENDER).await.unwrap(), - BigUint::from(0u64) - ); - - println!("Difficulty: {:?}", chain.get_difficulty().await); - - println!( - "Funds for address: {:?} {:?}", - SENDER, - blockchain.get_funds(SENDER).await.unwrap() - ); - - println!( - "Funds for address: {:?} {:?}", - ROOT_PUBLIC_ADDRESS, - blockchain.get_funds(&ROOT_PUBLIC_ADDRESS).await.unwrap() + amount_a, + U256::from_dec_str("10000000000").unwrap() - U256::from_dec_str("100").unwrap() ); - - chain.dump_config().await.unwrap(); - blockchain.dump_pool().await.unwrap(); -} - -#[test] -fn biguint_test() { - let num = BigUint::from_str("17239872183291832718372614872678146291748972189471829748921748") - .unwrap(); - let mut dump: Vec = Vec::new(); - tools::dump_biguint(&num, &mut dump).unwrap(); - - let loaded_num = tools::load_biguint(&dump).unwrap(); - - assert_eq!(loaded_num.0, num); - - let num = BigUint::from_str("0").unwrap(); - let mut dump: Vec = Vec::new(); - tools::dump_biguint(&num, &mut dump).unwrap(); - - let loaded_num = tools::load_biguint(&dump).unwrap(); - - assert_eq!(loaded_num.0, num); -} - -#[test] -fn transaction_block_test() { - let default_info = BasicInfo::new(500, vec![0], [1u8; 32], 0, [5u8; 32], *SENDER); - let tr = blockchaintree::transaction::Transaction::new( - *SENDER, - *RECIEVER, - 121212, - 2222222288u64.to_biguint().unwrap(), - *PREV_HASH, - ); - let block = TransactionBlock::new( - vec![tr.hash()], - 50.to_biguint().unwrap(), - default_info, - [1u8; 32], - ); - - let dump = block.dump().unwrap(); - - let loaded_block = TransactionBlock::parse(&dump[1..]).unwrap(); - - assert_eq!(block.hash().unwrap(), loaded_block.hash().unwrap()); -} - -#[test] -fn check_pow_test() { - check_pow(&[0u8; 32], &[1u8; 32], &[1]); -} - -#[test] -fn dump_genesis_block() { - let genesis_block = GenesisBlock {}; - - println!("{:?}", genesis_block.hash()); + assert_eq!(amount_b, U256::from_dec_str("100").unwrap()); } diff --git a/tests/chain_test.rs b/tests/chain_test.rs new file mode 100644 index 0000000..eab7a22 --- /dev/null +++ b/tests/chain_test.rs @@ -0,0 +1,149 @@ +use std::sync::Arc; + +use blockchaintree::{ + block, chain, tools, + transaction::{self, Transactionable}, +}; +use primitive_types::U256; + +#[tokio::test] +async fn init_flush_get_block_by_height_chain_test() { + let main_chain = chain::MainChain::new().unwrap(); + + main_chain.flush().await.unwrap(); + + drop(main_chain); + + let main_chain = chain::MainChain::new().unwrap(); + + let height = main_chain.get_height(); + + // generate block + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: unsafe { [0; 32].try_into().unwrap_unchecked() }, + height, + difficulty: [101; 32], + founder: [6; 33], + }; + let main_block = block::TransactionBlock::new( + U256::from_dec_str("1").unwrap(), + basic_data, + [0; 32], + vec![[0; 32], [1; 32]], + ); + + main_chain.add_block(Arc::new(main_block)).unwrap(); + + let height = main_chain.get_height(); + let block = main_chain.find_by_height(&(height - 1)).unwrap(); + + assert!(block.is_some()); + + let block = block.unwrap(); + + assert_eq!([6; 33], *block.get_founder()); + assert_eq!(160000, block.get_info().timestamp); + assert_eq!([0; 32], block.get_info().pow); + assert_eq!(height - 1, block.get_info().height); + assert_eq!([101; 32], block.get_info().difficulty); + assert_eq!(U256::from_dec_str("1").unwrap(), block.get_fee()); + assert_eq!([0; 32], block.get_merkle_root()); +} + +#[tokio::test] +async fn init_get_transaction_chain_test() { + let main_chain = chain::MainChain::new().unwrap(); + + let transaction = transaction::Transaction::new_signed( + [10; 33], + [20; 33], + 100, + U256::from_dec_str("3627836287").unwrap(), + U256::from_dec_str("3627836287").unwrap(), + Some(vec![228, 123]), + [33; 64], + ); + + main_chain.add_transactions(&[transaction.clone()]).unwrap(); + + let got_transaction = main_chain + .get_transaction(&tools::hash(&transaction.dump().unwrap())) + .unwrap() + .unwrap(); + + assert_eq!(transaction.get_data(), got_transaction.get_data()); + assert_eq!(transaction.get_amount(), got_transaction.get_amount()); + assert_eq!(transaction.get_sender(), got_transaction.get_sender()); + assert_eq!(transaction.get_receiver(), got_transaction.get_receiver()); + assert_eq!(transaction.get_dump_size(), got_transaction.get_dump_size()); + assert_eq!(transaction.get_timestamp(), got_transaction.get_timestamp()); + assert_eq!(transaction.get_signature(), got_transaction.get_signature()); +} + +#[tokio::test] +async fn init_flush_get_block_by_height_deriv_chain_test() { + let deriv_chain = chain::DerivativeChain::new( + "deadbeef", + &[ + 57, 26, 43, 126, 188, 137, 234, 205, 234, 97, 128, 221, 242, 186, 198, 206, 3, 25, 250, + 35, 169, 60, 208, 8, 94, 13, 60, 218, 72, 73, 207, 80, + ], + ) + .unwrap(); + + deriv_chain.flush().await.unwrap(); + drop(deriv_chain); + + let deriv_chain = chain::DerivativeChain::new( + "deadbeef", + &[ + 57, 26, 43, 126, 188, 137, 234, 205, 234, 97, 128, 221, 242, 186, 198, 206, 3, 25, 250, + 35, 169, 60, 208, 8, 94, 13, 60, 218, 72, 73, 207, 80, + ], + ) + .unwrap(); + + // generate block + let basic_data = block::BasicInfo { + timestamp: 160000, + pow: [0; 32], + previous_hash: unsafe { [0; 32].try_into().unwrap_unchecked() }, + height: U256::from_dec_str("0").unwrap(), + difficulty: [101; 32], + founder: [6; 33], + }; + let derivative_block = block::DerivativeBlock { + default_info: basic_data, + }; + deriv_chain.add_block(&derivative_block).unwrap(); + + let block = deriv_chain.find_by_height(&U256::zero()).unwrap(); + + assert!(block.is_some()); + + let block = block.unwrap(); + + assert_eq!( + derivative_block.default_info.timestamp, + block.default_info.timestamp + ); + assert_eq!(derivative_block.default_info.pow, block.default_info.pow); + assert_eq!( + derivative_block.default_info.previous_hash, + block.default_info.previous_hash + ); + assert_eq!( + derivative_block.default_info.height, + block.default_info.height + ); + assert_eq!( + derivative_block.default_info.difficulty, + block.default_info.difficulty + ); + assert_eq!( + derivative_block.default_info.founder, + block.default_info.founder + ); +} diff --git a/tests/tools_test.rs b/tests/tools_test.rs new file mode 100644 index 0000000..9c235f1 --- /dev/null +++ b/tests/tools_test.rs @@ -0,0 +1,15 @@ +use blockchaintree::tools::{self, check_pow}; + +#[test] +fn check_pow_test() { + let hash: [u8; 32] = [0x98, 0x2D, 0x9E, 0x3E, 0xB9, 0x96, 0xF5, 0x59, 0xE6, 0x33, 0xF4, 0xD1, 0x94, 0xDE, 0xF3, 0x76, 0x1D, 0x90, 0x9F, 0x5A, 0x3B, 0x64, 0x7D, 0x1A, 0x85, 0x1F, 0xEA, 0xD6, 0x7C, 0x32, 0xC9, 0xD1]; + + assert_eq!( + check_pow(&hash, &[0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], &[0x0, 0x7A, 0x9, 0xDE, 0x81, 0x32, 0x58, 0x4F, 0x6D, 0xE8]), + false + ); + assert_eq!( + check_pow(&hash, &[0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], &[0x3A, 0x91, 0x24, 0x45, 0xC9, 0x65, 0x60, 0xD5, 0x1E, 0x69]), + true + ); +} \ No newline at end of file diff --git a/tests/transaction_test.rs b/tests/transaction_test.rs new file mode 100644 index 0000000..1daf34e --- /dev/null +++ b/tests/transaction_test.rs @@ -0,0 +1,73 @@ +use blockchaintree::transaction::{self, Transactionable}; +use primitive_types::U256; +use secp256k1::Secp256k1; + +#[test] +fn dump_parse_transaction() { + let transaction = transaction::Transaction::new_signed( + [10; 33], + [20; 33], + 100, + U256::from_dec_str("3627836287").unwrap(), + U256::from_dec_str("3627836287").unwrap(), + None, + [33; 64], + ); + + let dump = transaction.dump().unwrap(); + + let parsed_transaction = transaction::Transaction::parse(&dump[1..]).unwrap(); + + assert_eq!(transaction.get_amount(), parsed_transaction.get_amount()); + assert_eq!(transaction.get_data(), parsed_transaction.get_data()); + assert_eq!( + transaction.get_receiver(), + parsed_transaction.get_receiver() + ); + assert_eq!(transaction.get_sender(), parsed_transaction.get_sender()); + assert_eq!( + transaction.get_signature(), + parsed_transaction.get_signature() + ); + assert_eq!( + transaction.get_timestamp(), + parsed_transaction.get_timestamp() + ); + + println!("{:?}", parsed_transaction); +} + +#[test] +fn hash_transaction() { + let transaction = transaction::Transaction::new_signed( + [10; 33], + [20; 33], + 100, + U256::from_dec_str("3627836287").unwrap(), + U256::from_dec_str("3627836287").unwrap(), + None, + [33; 64], + ); + + let hash = transaction.hash(); + + println!("{:?}", hash); +} + +#[test] +fn sign_verify_transaction() { + let secp = Secp256k1::new(); + let (secret_key, public_key) = secp.generate_keypair(&mut rand::thread_rng()); + + let transaction = transaction::Transaction::new( + public_key.serialize(), + public_key.serialize(), + 100, + U256::from_dec_str("3627836287").unwrap(), + U256::from_dec_str("3627836287").unwrap(), + secret_key.secret_bytes(), + Some(vec![1, 3, 3, 3, 3, 3, 3]), + ); + + assert!(transaction.verify().unwrap()); +}