diff --git a/.gitignore b/.gitignore index 08a1fa28..50b04048 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,11 @@ *.swn *.DS_Store +# Visual Studio Code stuff +/.vscode + +# GitEye stuff +/.project + # idea ide .idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..756d5b33 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +sudo: required +dist: trusty +language: rust + +branches: + only: + - master +matrix: + fast_finish: false + include: + - rust: stable +cache: + apt: true + directories: + - $TRAVIS_BUILD_DIR/target + - $HOME/.cargo +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - gcc-4.8 + - g++-4.8 +script: + - ./tools/test.sh +after_success: | + [ false ] && + [ $TRAVIS_BRANCH = master ] && + [ $TRAVIS_PULL_REQUEST = false ] && + [ $TRAVIS_RUST_VERSION = stable ] && + cargo doc --no-deps --verbose && + echo '' > target/doc/index.html && + pip install --user ghp-import && + /home/travis/.local/bin/ghp-import -n target/doc && + git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages +env: + global: + - CXX="g++-4.8" + - CC="gcc-4.8" + - secure: IbTkR3KY7N9e3lc6hreWuY2mhuKyf7k6LSRUMMrOeb1k9TIncYMbR6Sl2W1t5+CJVp7WqVoUbP9YIH8o8a9BBuFldTyAz3tapa908KrZKIRlb/uCTef11z/jzasye4Ttj+358DBrCXZqueFw5Bh5Bnaie3r5bSYWTcQU693Bm/tKFp0lh4fSjzGyk7kRZ4V4rz2YHudZOcNIXN+QfY8aY6dxQraw82GUnNXJLzGiC5GJgeSV1uSnxMUM/lb4+zP1qRlcLBLnwHSTH/3vHH5xsOaRwHEYOQT9pTDyxMnJzDgYfjH4mNu3jSvZ7WFrP+Sza/yR+3sjHpOCFN6rsWc88iTq6Nwp+ESfyjHiLT+jqWs7r57sg2VfwHXuzoEW5GGagrIoF/pDEWJvhtNRYvdYevDtlZYPTfhSR4WOPkMPCg7Ln2W7a7vrSH2iRNxawUDnq3bOIyGGBeGwBJFDOEd6CmP+ojjUUm9L5I4berYACgYEDuZ4bpRX3WpGR1yAhSF4o5BTh+88EWU/VhL2ceXreHlztma3KQ5526Ip46lC4eLFrl/w64zaupesEZFjLsOFm7U9Vx9IM6aqBzOFr8Mt8DlPLaTRmRrbzPRYqHg0MLpHkH8S/HyNbK0xvqbQFRHQ6XPXNJVJXZPz8XPUYWYPK9ayplVdRN9nonHQO7F0gV8= diff --git a/Cargo.lock b/Cargo.lock index ffccd8aa..b82da87f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ dependencies = [ "db 0.1.0", "keys 0.1.0", "message 0.1.0", + "miner 0.1.0", "p2p 0.1.0", "script 0.1.0", ] @@ -213,6 +214,15 @@ dependencies = [ "serialization 0.1.0", ] +[[package]] +name = "miner" +version = "0.1.0" +dependencies = [ + "chain 0.1.0", + "primitives 0.1.0", + "serialization 0.1.0", +] + [[package]] name = "mio" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 3d81cda7..4c6732e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ description = "Parity bitcoin client." clap = { version = "2", features = ["yaml"] } keys = { path = "keys" } message = { path = "message" } +miner = { path = "miner" } p2p = { path = "p2p" } script = { path = "script" } db = { path = "db" } diff --git a/README.md b/README.md index d300a4b4..41b18b54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # parity-bitcoin The Parity Bitcoin client +[![Build Status][travis-image]][travis-url] + +[Internal Documentation][doc-url] + ![Graph][graph] [graph]: https://github.com/ethcore/parity-bitcoin/blob/master/tools/graph.png +[travis-image]: https://travis-ci.com/ethcore/parity-bitcoin.svg?token=DMFvZu71iaTbUYx9UypX&branch=master +[travis-url]: https://travis-ci.com/ethcore/parity-bitcoin +[doc-url]: https://ethcore.github.io/parity-bitcoin/pbtc/index.html diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index 863ffea4..8912a62b 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -6,7 +6,7 @@ use hex::FromHex; use bytes::Bytes; use ser::{ Deserializable, Reader, Error as ReaderError, deserialize, - Serializable, Stream, serialize + Serializable, Stream, serialize, serialized_list_size }; use crypto::dhash256; use hash::H256; @@ -42,6 +42,11 @@ impl Serializable for OutPoint { .append(&self.hash) .append(&self.index); } + + #[inline] + fn serialized_size(&self) -> usize { + self.hash.serialized_size() + self.index.serialized_size() + } } impl Deserializable for OutPoint { @@ -79,6 +84,13 @@ impl Serializable for TransactionInput { .append(&self.script_sig) .append(&self.sequence); } + + #[inline] + fn serialized_size(&self) -> usize { + self.previous_output.serialized_size() + + self.script_sig.serialized_size() + + self.sequence.serialized_size() + } } impl Deserializable for TransactionInput { @@ -119,6 +131,12 @@ impl Serializable for TransactionOutput { .append(&self.value) .append(&self.script_pubkey); } + + #[inline] + fn serialized_size(&self) -> usize { + self.value.serialized_size() + + self.script_pubkey.serialized_size() + } } impl Deserializable for TransactionOutput { @@ -167,6 +185,14 @@ impl Serializable for Transaction { .append_list(&self.outputs) .append(&self.lock_time); } + + #[inline] + fn serialized_size(&self) -> usize { + self.version.serialized_size() + + serialized_list_size(&self.inputs) + + serialized_list_size(&self.outputs) + + self.lock_time.serialized_size() + } } impl Deserializable for Transaction { @@ -205,6 +231,7 @@ impl Transaction { #[cfg(test)] mod tests { use hash::H256; + use ser::Serializable; use super::Transaction; // real transaction from block 80000 @@ -231,4 +258,11 @@ mod tests { let hash = H256::from_reversed_str("5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2"); assert_eq!(t.hash(), hash); } + + #[test] + fn test_transaction_serialized_len() { + let raw_tx: &'static str = "0100000001a6b97044d03da79c005b20ea9c0e1a6d9dc12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac00000000"; + let tx: Transaction = raw_tx.into(); + assert_eq!(tx.serialized_size(), raw_tx.len() / 2); + } } diff --git a/miner/Cargo.toml b/miner/Cargo.toml new file mode 100644 index 00000000..c909881b --- /dev/null +++ b/miner/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "miner" +version = "0.1.0" +authors = ["Ethcore "] + +[dependencies] +chain = { path = "../chain" } +primitives = { path = "../primitives" } +serialization = { path = "../serialization" } diff --git a/miner/src/lib.rs b/miner/src/lib.rs new file mode 100644 index 00000000..2462ef2b --- /dev/null +++ b/miner/src/lib.rs @@ -0,0 +1,9 @@ +extern crate chain; +extern crate primitives; +extern crate serialization as ser; + +pub mod memory_pool; + +pub use primitives::{hash}; + +pub use self::memory_pool::{MemoryPool, Information as MemoryPoolInformation}; \ No newline at end of file diff --git a/miner/src/memory_pool.rs b/miner/src/memory_pool.rs new file mode 100644 index 00000000..f9669de0 --- /dev/null +++ b/miner/src/memory_pool.rs @@ -0,0 +1,647 @@ +//! Transactions memory pool +//! +//! `MemoryPool` keeps track of all transactions seen by the node (received from other peers) and own transactions +//! and orders them by given strategies. It works like multi-indexed priority queue, giving option to pop 'top' +//! transactions. +//! It also guarantees that ancestor-descendant relation won't break during ordered removal (ancestors always removed +//! before descendants). Removal using remove_by_hash can break this rule. +use std::rc::Rc; +use hash::H256; +use chain::Transaction; +use std::collections::HashMap; +use std::collections::HashSet; +use ser::Serializable; + +/// Transactions ordering strategy +#[derive(Debug)] +pub enum OrderingStrategy { + /// Order transactions by their timestamp + ByTimestamp, + /// Order transactions by miner score + ByMinerScore, +} + +/// Information on current `MemoryPool` state +#[derive(Debug)] +pub struct Information { + /// The number of transactions currently in the `MemoryPool` + pub transactions_count: usize, + /// The total number of bytes in the transactions in the `MemoryPool` + pub transactions_size_in_bytes: usize, +} + +/// Transactions memory pool +#[derive(Debug)] +pub struct MemoryPool { + /// Transactions storage + storage: Storage, +} + +/// Single entry +#[derive(Debug)] +pub struct Entry { + /// Transaction + transaction: Transaction, + /// Transaction ancestors hashes + ancestors: Rc>, + /// Transaction hash (stored for effeciency) + hash: H256, + /// Transaction size (stored for effeciency) + size: usize, + /// 'Timestamp' when transaction has entered memory pool + timestamp: u64, + /// Transaction fee (stored for efficiency) + miner_fee: i64, + /// Virtual transaction fee (a way to prioritize/penalize transaction) + miner_virtual_fee: i64, +} + +/// Multi-index transactions storage +#[derive(Debug)] +struct Storage { + /// Transactions counter (for timestamp ordering) + counter: u64, + /// Total transactions size (when serialized) in bytes + transactions_size_in_bytes: usize, + /// By-input storage + by_input: HashMap>, + /// By-hash storage + by_hash: HashMap, + /// By-entry-time storage + by_timestamp: timestamp_strategy::Storage, + /// By-score storage + by_miner_score: miner_score_strategy::Storage, +} + +macro_rules! ordering_strategy { + ($strategy: ident; $($member: ident: $member_type: ty), *; $comparer: expr) => { + mod $strategy { + use std::rc::Rc; + use std::cmp::Ordering; + use hash::H256; + use std::collections::HashSet; + use std::collections::BTreeSet; + use super::Entry; + + /// Lightweight struct maintain transactions ordering + #[derive(Debug, Eq, PartialEq)] + pub struct OrderedEntry { + /// Transaction hash + hash: H256, + /// Transaction ancestors + ancestors: Rc>, + /// Transaction data + $($member: $member_type), * + } + + impl OrderedEntry { + pub fn for_entry(entry: &Entry) -> OrderedEntry { + OrderedEntry { + hash: entry.hash.clone(), + ancestors: entry.ancestors.clone(), + $($member: entry.$member.clone()), * + } + } + } + + impl PartialOrd for OrderedEntry { + fn partial_cmp(&self, other: &OrderedEntry) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for OrderedEntry { + fn cmp(&self, other: &Self) -> Ordering { + if self.ancestors.contains(&other.hash) { + return Ordering::Greater + } + if other.ancestors.contains(&self.hash) { + return Ordering::Less + } + + let order = $comparer(&self, other); + if order != Ordering::Equal { + return order + } + + self.hash.cmp(&other.hash) + } + } + + /// By-timestamp ordering storage + #[derive(Debug)] + pub struct Storage { + data: BTreeSet, + } + + impl Storage { + pub fn new() -> Self { + Storage { + data: BTreeSet::new() + } + } + + /// Insert entry to storage + pub fn insert(&mut self, entry: &Entry) { + self.data.replace(OrderedEntry::for_entry(entry)); + } + + /// Remove entry from storage + pub fn remove(&mut self, entry: &Entry) { + self.data.remove(&OrderedEntry::for_entry(entry)); + } + + /// An iterator that takes first n entries using predicate + pub fn take(&self, n: usize) -> Vec { + self.data.iter() + .map(|ref entry| entry.hash.clone()) + .take(n) + .collect() + } + } + } + } +} + +ordering_strategy!(timestamp_strategy; + timestamp: u64; + |me: &Self, other: &Self| + me.timestamp.cmp(&other.timestamp)); +ordering_strategy!(miner_score_strategy; + size: usize, miner_fee: i64, miner_virtual_fee: i64; + |me: &Self, other: &Self| { + // lesser miner score means later removal + let left = (me.miner_fee + me.miner_virtual_fee) * (other.size as i64); + let right = (other.miner_fee + other.miner_virtual_fee) * (me.size as i64); + right.cmp(&left) + }); + +macro_rules! insert_to_orderings { + ($me: expr, $entry: expr) => ( + $me.by_timestamp.insert(&$entry); + $me.by_miner_score.insert(&$entry); + ) +} + +macro_rules! remove_from_orderings { + ($me: expr, $entry: expr) => ( + $me.by_timestamp.remove(&$entry); + $me.by_miner_score.remove(&$entry); + ) +} + +impl Storage { + pub fn new() -> Self { + Storage { + counter: 0, + transactions_size_in_bytes: 0, + by_input: HashMap::new(), + by_hash: HashMap::new(), + by_timestamp: timestamp_strategy::Storage::new(), + by_miner_score: miner_score_strategy::Storage::new(), + } + } + + pub fn insert(&mut self, entry: Entry) { + self.transactions_size_in_bytes += entry.size; + + self.update_by_input_on_insert(&entry); + self.update_descendants_on_insert(&entry); + + insert_to_orderings!(self, entry); + self.by_hash.insert(entry.hash.clone(), entry); + } + + pub fn remove(&mut self, entry: &Entry) -> Option { + self.transactions_size_in_bytes -= entry.size; + + self.update_descendants_on_remove(&entry); + self.update_by_input_on_remove(&entry); + + remove_from_orderings!(self, entry); + self.by_hash.remove(&entry.hash) + } + + pub fn get_by_hash(&self, h: &H256) -> Option<&Entry> { + self.by_hash.get(h) + } + + pub fn contains(&self, hash: &H256) -> bool { + self.by_hash.contains_key(hash) + } + + pub fn remove_by_hash(&mut self, h: &H256) -> Option { + self.by_hash.remove(h) + .map(|entry| { + self.remove(&entry); + entry + }) + } + + pub fn remove_n_with_strategy(&mut self, n: usize, strategy: OrderingStrategy) -> Vec { + let hashes = { + match strategy { + OrderingStrategy::ByTimestamp => self.by_timestamp.take(n), + OrderingStrategy::ByMinerScore => self.by_miner_score.take(n), + } + }; + hashes + .iter() + .map(|ref hash| { + let entry = self.remove_by_hash(&hash) + .expect("`hash` is read from by_* index; all hashes from indexes are also stored in `by_hash`; remove returns entry from `by_hash`; qed"); + entry.transaction + }) + .collect() + } + + pub fn set_virtual_fee(&mut self, h: &H256, virtual_fee: i64) { + if let Some(ref mut entry) = self.by_hash.get_mut(h) { + self.by_miner_score.remove(&entry); + entry.miner_virtual_fee = virtual_fee; + self.by_miner_score.insert(&entry); + } + } + + pub fn get_transactions_ids(&self) -> Vec { + self.by_hash.keys().map(|h| h.clone()).collect() + } + + fn update_by_input_on_insert(&mut self, entry: &Entry) { + // maintain map { ancestor: list } to support inserting + // acendants & descendants in random order + for input_hash in entry.transaction.inputs.iter().map(|input| &input.previous_output.hash) { + self.by_input.entry(input_hash.clone()).or_insert_with(|| HashSet::new()).insert(entry.hash.clone()); + } + } + + fn update_descendants_on_insert(&mut self, entry: &Entry) { + // this code will run only when ancestor transaction is inserted + // in memory pool after its descendants + if let Some(descendants) = self.by_input.get(&entry.hash) { + for descendant in descendants.iter() { + if let Some(mut descendant_entry) = self.by_hash.remove(descendant) { + { + remove_from_orderings!(self, descendant_entry); + + let ancestors = Rc::make_mut(&mut descendant_entry.ancestors); + ancestors.insert(entry.hash.clone()); + } + insert_to_orderings!(self, descendant_entry); + self.by_hash.insert(descendant_entry.hash.clone(), descendant_entry); + }; + } + } + } + + fn update_by_input_on_remove(&mut self, entry: &Entry) { + for input_hash in entry.transaction.inputs.iter().map(|input| &input.previous_output.hash) { + let remove_entry = { + let by_input_item = self.by_input.get_mut(input_hash) + .expect("`entry.transaction` is immutable; on insert all transaction' inputs are stored in `by_input`; `by_input` only modified here; qed"); + by_input_item.remove(&entry.hash); + by_input_item.is_empty() + }; + if remove_entry { + self.by_input.remove(input_hash); + } + } + } + + fn update_descendants_on_remove(&mut self, entry: &Entry) { + // this code will run when there still are dependent transactions in the pool + if let Some(descendants) = self.by_input.get(&entry.hash) { + for descendant in descendants.iter() { + if let Some(mut descendant_entry) = self.by_hash.remove(descendant) { + { + remove_from_orderings!(self, descendant_entry); + + let ancestors = Rc::make_mut(&mut descendant_entry.ancestors); + ancestors.remove(&entry.hash); + } + insert_to_orderings!(self, descendant_entry); + self.by_hash.insert(descendant_entry.hash.clone(), descendant_entry); + }; + } + } + } +} + +impl MemoryPool { + /// Creates new memory pool + pub fn new() -> Self { + MemoryPool { + storage: Storage::new(), + } + } + + /// Insert verified transaction to the `MemoryPool` + pub fn insert_verified(&mut self, t: Transaction) { + let entry = self.make_entry(t); + self.storage.insert(entry); + } + + /// Removes single transaction by its hash. + /// All descedants remain in the pool. + pub fn remove_by_hash(&mut self, h: &H256) -> Option { + self.storage.remove_by_hash(h).map(|entry| entry.transaction) + } + + /// Removes up to n transactions the `MemoryPool`, using selected strategy. + /// Ancestors are always removed before descendant transactions. + pub fn remove_n_with_strategy(&mut self, n: usize, strategy: OrderingStrategy) -> Vec { + self.storage.remove_n_with_strategy(n, strategy) + } + + /// Set miner virtual fee for transaction + pub fn set_virtual_fee(&mut self, h: &H256, virtual_fee: i64) { + self.storage.set_virtual_fee(h, virtual_fee) + } + + /// Get transaction by hash + pub fn get(&self, hash: &H256) -> Option<&Transaction> { + self.storage.get_by_hash(hash).map(|ref entry| &entry.transaction) + } + + /// Checks if transaction is in the mempool + pub fn contains(&self, hash: &H256) -> bool { + self.storage.contains(hash) + } + + /// Returns information on `MemoryPool` (as in GetMemPoolInfo RPC) + /// https://bitcoin.org/en/developer-reference#getmempoolinfo + pub fn information(&self) -> Information { + Information { + transactions_count: self.storage.by_hash.len(), + transactions_size_in_bytes: self.storage.transactions_size_in_bytes + } + } + + /// Returns TXIDs of all transactions in `MemoryPool` (as in GetRawMemPool RPC) + /// https://bitcoin.org/en/developer-reference#getrawmempool + pub fn get_transactions_ids(&self) -> Vec { + self.storage.get_transactions_ids() + } + + fn make_entry(&mut self, t: Transaction) -> Entry { + let hash = t.hash(); + let ancestors = Rc::new(self.get_ancestors(&t)); + let size = self.get_transaction_size(&t); + let timestamp = self.get_timestamp(); + let miner_fee = self.get_transaction_miner_fee(&t); + Entry { + transaction: t, + hash: hash, + ancestors: ancestors, + size: size, + timestamp: timestamp, + miner_fee: miner_fee, + miner_virtual_fee: 0, + } + } + + fn get_ancestors(&self, t: &Transaction) -> HashSet { + let mut ancestors: HashSet = HashSet::new(); + let ancestors_entries = t.inputs.iter() + .filter_map(|ref input| self.storage.get_by_hash(&input.previous_output.hash)); + for ancestor_entry in ancestors_entries { + ancestors.insert(ancestor_entry.hash.clone()); + for grand_ancestor in ancestor_entry.ancestors.iter() { + ancestors.insert(grand_ancestor.clone()); + } + } + ancestors + } + + fn get_transaction_size(&self, t: &Transaction) -> usize { + t.serialized_size() + } + + fn get_transaction_miner_fee(&self, t: &Transaction) -> i64 { + let input_value = 0; // TODO: sum all inputs of transaction + let output_value = t.outputs.iter().fold(0, |acc, ref output| acc + output.value); + (output_value - input_value) as i64 + } + + #[cfg(not(test))] + fn get_timestamp(&mut self) -> u64 { + self.storage.counter += 1; + self.storage.counter + } + + #[cfg(test)] + fn get_timestamp(&self) -> u64 { + (self.storage.by_hash.len() % 3usize) as u64 + } +} + +#[cfg(test)] +mod tests { + use std::cmp::Ordering; + use chain::Transaction; + use super::{MemoryPool, OrderingStrategy}; + + // output_value = 898126612, size = 225, miner_score ~ 3991673.83 + const RAW_TRANSACTION1: &'static str = "01000000017e4e1bfa4cc16a46593390b9f627db9a3b7c3b1802daa826fc0c477c067ea4f1000000006a47304402203cca1b01b307d3dba3d4f819ef4e9ccf839fa7ef901fc39d2b1d6e33c159a0b0022009d135bd47b8465a69f4db7145af34e0f063e926d95d9c21fb4e8cbc2052838c0121039c01d413f0e296cb766b408c528d3526e75a0f63cfc44c1147160613a12e6cb7feffffff02c8f27535000000001976a914e54788b730b91eb9b29917fa10ddbc97996a987988ac4c601200000000001976a91421e8ed0ddc9a365c10fa3130c91c36237a45848888aca7a00600"; + // output_value = 423675406, size = 225, miner_score ~ 1883001.80 + const RAW_TRANSACTION2: &'static str = "0100000001efaf295b354d8063336a03652664e31b63666f6fbe51b377ad3bdd7b65678a43000000006a47304402206100084828cfc2b71881f1a99447658d5844043f69c1f9bd6c95cb0e11197d4002207c8e23b6233e7317fe0db3e12c8a4291efe671e02c4bbeeb3534e208bb9a3a75012103e091c9c970427a709646990ca16d4d418efc9e5c46ae794c3d09023a7e0e1c57feffffff0226662e19000000001976a914ffad82403bc2a1dd3789b7e653d352515ae86b7288ace85f1200000000001976a914c2f8a6513ebcf6f61edca10199442e33108d540988aca7a00600"; + // output_value = 668388826, size = 225, miner_score ~ 2970617.00 + const RAW_TRANSACTION3: &'static str = "01000000012eee0922c7385e6a11d4f92da65e16b205b2cdfe294c3140c3bc0427f6c11794000000006a47304402207aab34b1c9bb5464c3c1ddf9fee042d8755b81ed1e8c35b895ee2d7da17a23ac02205dfd9c8d14f44951c9e8c3a70f8820a6b113046db1ca25d52a31a4b68d62e07901210306a47c3c0ce5ad78616ad0695113ee4d7a848155fd9f4a1eb7aeed756e174211feffffff024c601200000000001976a914ae829d4d1a8945dc165a57f1149b4656e48c161988ac8e6dc427000000001976a9146c4f1f52adec5211f456acc12917fe902949b08088aca7a00600"; + // output_value = 256505044, size = 226, miner_score ~ 1134978.07 + const RAW_TRANSACTION4: &'static str = "01000000012969032b2a920c03fa71eeea5250d0f6259b130a15ed695f79d957e47b9b2d8b000000006b483045022100b34c8a714b295b84686211b37daaa65ef79cf0ce1dc7d7a4e133a5b08a308f6f02201abca9e08bddb56e205bd1c5e689419926031ec955d8c89671a16a7076dce0ec0121020db95d68c760d1e0a5090dbf0ed2cbfd1225c3bc46d4e31e272bcc14b42a9643feffffff021896370f000000001976a914552b2549642c22462dc0e82ea25500dea6bb5e2188acbc5e1200000000001976a914dca40d275f5eb00cefab813925a1b07b9b77159188aca7a00600"; + const RAW_TRANSACTION1_HASH: &'static str = "af9d5d566b6c914fc0de758a5f18731f5570ac59d1f0c5d1516f0f2dda2c3f79"; + const RAW_TRANSACTION2_HASH: &'static str = "e97719095d91821f691dcbebdf5fb82c4eff8dd33d0c3cc6690aae37ed82a01e"; + const RAW_TRANSACTION3_HASH: &'static str = "2d3833b35efc8f2b9d1c2140505b207fd71155178ad604e03364448f7007fc04"; + const RAW_TRANSACTION4_HASH: &'static str = "4a15e0b41b1a47381f2f2baa16087688d1cd9078416960deed1faae852a469ce"; + + fn construct_memory_pool() -> MemoryPool { + let transaction1: Transaction = RAW_TRANSACTION1.into(); + let transaction2: Transaction = RAW_TRANSACTION2.into(); + let transaction3: Transaction = RAW_TRANSACTION3.into(); + let transaction4: Transaction = RAW_TRANSACTION4.into(); + let transaction1_hash = RAW_TRANSACTION1_HASH.into(); + let transaction4_hash = RAW_TRANSACTION4_HASH.into(); + assert_eq!(transaction1.hash(), transaction1_hash); + assert_eq!(transaction2.hash(), RAW_TRANSACTION2_HASH.into()); + assert_eq!(transaction3.hash(), RAW_TRANSACTION3_HASH.into()); + assert_eq!(transaction4.hash(), transaction4_hash); + + // hash of t4 must be lesser than hash of t4 + assert_eq!(transaction4_hash.cmp(&transaction1_hash), Ordering::Less); + + let mut pool = MemoryPool::new(); + pool.insert_verified(RAW_TRANSACTION1.into()); + pool.insert_verified(RAW_TRANSACTION2.into()); + pool.insert_verified(RAW_TRANSACTION3.into()); + pool.insert_verified(RAW_TRANSACTION4.into()); + + pool + } + + #[test] + fn test_memory_pool_remove_by_hash() { + let mut pool = construct_memory_pool(); + + let pool_transactions = pool.get_transactions_ids(); + assert_eq!(pool_transactions.len(), 4); + + // check pool transactions + let ref hash_to_remove = pool_transactions[0]; + assert!(*hash_to_remove == RAW_TRANSACTION1_HASH.into() + || *hash_to_remove == RAW_TRANSACTION2_HASH.into() + || *hash_to_remove == RAW_TRANSACTION3_HASH.into() + || *hash_to_remove == RAW_TRANSACTION4_HASH.into()); + assert_eq!(pool.get_transactions_ids().len(), 4); + + // remove and check remaining transactions + let removed = pool.remove_by_hash(&hash_to_remove); + assert!(removed.is_some()); + assert_eq!(removed.unwrap().hash(), *hash_to_remove); + assert_eq!(pool.get_transactions_ids().len(), 3); + } + + #[test] + fn test_memory_pool_transaction_dependent_transactions_parent_after_child() { + let parent_transaction: Transaction = "00000000000164000000000000000000000000".into(); + let child_transaction: Transaction = "0000000001545ac9cffeaa3ee074f08a5306e703cb30883192ed9b10ee9ddb76824e4985070000000000000000000000000000".into(); + let grandchild_transaction: Transaction = "0000000001cc1d8279403880bfdd7682c28ed8441a138f96ae1dc5fd90bc928b88d48107a90000000000000000000164000000000000000000000000".into(); + let parent_transaction_hash = parent_transaction.hash(); + let child_transaction_hash = child_transaction.hash(); + let grandchild_transaction_hash = grandchild_transaction.hash(); + + // insert child, then parent + let mut pool = MemoryPool::new(); + pool.insert_verified(grandchild_transaction); // timestamp 0 + pool.insert_verified(child_transaction); // timestamp 1 + pool.insert_verified(parent_transaction); // timestamp 2 + + // check that parent transaction was removed before child trnasaction + let transactions = pool.remove_n_with_strategy(3, OrderingStrategy::ByTimestamp); + assert_eq!(transactions.len(), 3); + assert_eq!(transactions[0].hash(), parent_transaction_hash); + assert_eq!(transactions[1].hash(), child_transaction_hash); + assert_eq!(transactions[2].hash(), grandchild_transaction_hash); + } + + #[test] + fn test_memory_pool_transaction_dependent_transactions_parent_before_child() { + let parent_transaction: Transaction = "00000000000164000000000000000000000000".into(); + let child_transaction: Transaction = "0000000001545ac9cffeaa3ee074f08a5306e703cb30883192ed9b10ee9ddb76824e4985070000000000000000000164000000000000000000000000".into(); + let grandchild_transaction: Transaction = "0000000001cc1d8279403880bfdd7682c28ed8441a138f96ae1dc5fd90bc928b88d48107a90000000000000000000164000000000000000000000000".into(); + let parent_transaction_hash = parent_transaction.hash(); + let child_transaction_hash = child_transaction.hash(); + let grandchild_transaction_hash = grandchild_transaction.hash(); + + // insert child, then parent + let mut pool = MemoryPool::new(); + pool.insert_verified(parent_transaction); // timestamp 0 + pool.insert_verified(child_transaction); // timestamp 1 + pool.insert_verified(grandchild_transaction); // timestamp 2 + + // check that parent transaction was removed before child trnasaction + let transactions = pool.remove_n_with_strategy(3, OrderingStrategy::ByTimestamp); + assert_eq!(transactions.len(), 3); + assert_eq!(transactions[0].hash(), parent_transaction_hash); + assert_eq!(transactions[1].hash(), child_transaction_hash); + assert_eq!(transactions[2].hash(), grandchild_transaction_hash); + } + + #[test] + fn test_memory_pool_transaction_dependent_transactions_insert_after_remove_by_hash() { + let raw_parent_transaction = "00000000000164000000000000000000000000"; + let raw_child_transaction = "0000000001545ac9cffeaa3ee074f08a5306e703cb30883192ed9b10ee9ddb76824e4985070000000000000000000164000000000000000000000000"; + let raw_grandchild_transaction = "0000000001cc1d8279403880bfdd7682c28ed8441a138f96ae1dc5fd90bc928b88d48107a90000000000000000000164000000000000000000000000"; + let parent_transaction: Transaction = raw_parent_transaction.into(); + let child_transaction: Transaction = raw_child_transaction.into(); + let grandchild_transaction: Transaction = raw_grandchild_transaction.into(); + let parent_transaction_hash = parent_transaction.hash(); + let child_transaction_hash = child_transaction.hash(); + let grandchild_transaction_hash = grandchild_transaction.hash(); + + // insert child, then parent + let mut pool = MemoryPool::new(); + pool.insert_verified(parent_transaction); + pool.insert_verified(child_transaction); + pool.insert_verified(grandchild_transaction); + + // remove child transaction & make sure that other transactions are still there + pool.remove_by_hash(&child_transaction_hash); + assert_eq!(pool.get_transactions_ids().len(), 2); + + // insert child transaction back to the pool & assert transactions are removed in correct order + pool.insert_verified(raw_child_transaction.into()); + let transactions = pool.remove_n_with_strategy(3, OrderingStrategy::ByMinerScore); + assert_eq!(transactions.len(), 3); + assert_eq!(transactions[0].hash(), parent_transaction_hash); + assert_eq!(transactions[1].hash(), child_transaction_hash); + assert_eq!(transactions[2].hash(), grandchild_transaction_hash); + } + + #[test] + fn test_memory_pool_get_information() { + let mut pool = construct_memory_pool(); + let pool_sizes = [901, 676, 451, 226, 0]; + let removals = [RAW_TRANSACTION1_HASH, RAW_TRANSACTION2_HASH, RAW_TRANSACTION3_HASH, RAW_TRANSACTION4_HASH]; + // check pool information after removing each transaction + for i in 0..pool_sizes.len() { + let expected_pool_count = 5 - i - 1; + let expected_pool_size = pool_sizes[i]; + let info = pool.information(); + assert_eq!(info.transactions_count, expected_pool_count); + assert_eq!(info.transactions_size_in_bytes, expected_pool_size); + + if expected_pool_size != 0 { + pool.remove_by_hash(&removals[i].into()); + } + } + } + + #[test] + fn test_memory_pool_timestamp_ordering_strategy() { + let mut pool = construct_memory_pool(); + + // remove transactions [4, 1, 2] (timestamps: [0, 0, 1]) + let transactions = pool.remove_n_with_strategy(3, OrderingStrategy::ByTimestamp); + assert_eq!(transactions.len(), 3); + assert_eq!(transactions[0].hash(), RAW_TRANSACTION4_HASH.into()); + assert_eq!(transactions[1].hash(), RAW_TRANSACTION1_HASH.into()); + assert_eq!(transactions[2].hash(), RAW_TRANSACTION2_HASH.into()); + assert_eq!(pool.get_transactions_ids().len(), 1); + + // remove transactions [3] (timestamps: [2]) + let transactions = pool.remove_n_with_strategy(3, OrderingStrategy::ByTimestamp); + assert_eq!(transactions.len(), 1); + assert_eq!(transactions[0].hash(), RAW_TRANSACTION3_HASH.into()); + } + + #[test] + fn test_memory_pool_miner_score_ordering_strategy() { + let mut pool = construct_memory_pool(); + + let transactions = pool.remove_n_with_strategy(4, OrderingStrategy::ByMinerScore); + assert_eq!(transactions.len(), 4); + assert_eq!(transactions[0].hash(), RAW_TRANSACTION1_HASH.into()); + assert_eq!(transactions[1].hash(), RAW_TRANSACTION3_HASH.into()); + assert_eq!(transactions[2].hash(), RAW_TRANSACTION2_HASH.into()); + assert_eq!(transactions[3].hash(), RAW_TRANSACTION4_HASH.into()); + assert_eq!(pool.get_transactions_ids().len(), 0); + } + + #[test] + fn test_memory_pool_miner_score_ordering_strategy_with_virtual_fee() { + let mut pool = construct_memory_pool(); + + // increase miner score of transaction 4 to move it to position #1 + pool.set_virtual_fee(&RAW_TRANSACTION4_HASH.into(), 1000000000); + // decrease miner score of transaction 3 to move it to position #4 + pool.set_virtual_fee(&RAW_TRANSACTION3_HASH.into(), -500000000); + + let transactions = pool.remove_n_with_strategy(4, OrderingStrategy::ByMinerScore); + assert_eq!(transactions.len(), 4); + assert_eq!(transactions[0].hash(), RAW_TRANSACTION4_HASH.into()); + assert_eq!(transactions[1].hash(), RAW_TRANSACTION1_HASH.into()); + assert_eq!(transactions[2].hash(), RAW_TRANSACTION2_HASH.into()); + assert_eq!(transactions[3].hash(), RAW_TRANSACTION3_HASH.into()); + assert_eq!(pool.remove_n_with_strategy(1, OrderingStrategy::ByMinerScore).len(), 0); + } +} diff --git a/primitives/src/hash.rs b/primitives/src/hash.rs index 4ddcfac6..f2795fa1 100644 --- a/primitives/src/hash.rs +++ b/primitives/src/hash.rs @@ -1,4 +1,5 @@ use std::{fmt, ops, cmp, str}; +use std::hash::{Hash, Hasher}; use hex::{ToHex, FromHex, FromHexError}; macro_rules! impl_hash { @@ -96,6 +97,8 @@ macro_rules! impl_hash { } } + impl Eq for $name {} + impl cmp::PartialEq for $name { fn eq(&self, other: &Self) -> bool { let self_ref: &[u8] = &self.0; @@ -104,6 +107,13 @@ macro_rules! impl_hash { } } + impl Hash for $name { + fn hash(&self, state: &mut H) where H: Hasher { + state.write(&self.0); + state.finish(); + } + } + impl $name { pub fn reversed(&self) -> Self { let mut result = self.clone(); diff --git a/serialization/src/compact_integer.rs b/serialization/src/compact_integer.rs index a02b3c0e..fa55b68e 100644 --- a/serialization/src/compact_integer.rs +++ b/serialization/src/compact_integer.rs @@ -82,6 +82,15 @@ impl Serializable for CompactInteger { } } } + + fn serialized_size(&self) -> usize { + match self.0 { + 0...0xfc => 1, + 0xfd...0xffff => 3, + 0x10000...0xffff_ffff => 5, + _ => 9, + } + } } impl Deserializable for CompactInteger { diff --git a/serialization/src/impls.rs b/serialization/src/impls.rs index 63c7c1ea..c378c363 100644 --- a/serialization/src/impls.rs +++ b/serialization/src/impls.rs @@ -9,6 +9,11 @@ impl Serializable for bool { fn serialize(&self, s: &mut Stream) { s.write_u8(*self as u8).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 1 + } } impl Serializable for i32 { @@ -16,6 +21,11 @@ impl Serializable for i32 { fn serialize(&self, s: &mut Stream) { s.write_i32::(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 4 + } } impl Serializable for i64 { @@ -23,6 +33,11 @@ impl Serializable for i64 { fn serialize(&self, s: &mut Stream) { s.write_i64::(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 8 + } } impl Serializable for u8 { @@ -30,6 +45,11 @@ impl Serializable for u8 { fn serialize(&self, s: &mut Stream) { s.write_u8(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 1 + } } impl Serializable for u16 { @@ -37,6 +57,11 @@ impl Serializable for u16 { fn serialize(&self, s: &mut Stream) { s.write_u16::(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 2 + } } impl Serializable for u32 { @@ -44,6 +69,11 @@ impl Serializable for u32 { fn serialize(&self, s: &mut Stream) { s.write_u32::(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 4 + } } impl Serializable for u64 { @@ -51,6 +81,11 @@ impl Serializable for u64 { fn serialize(&self, s: &mut Stream) { s.write_u64::(*self).unwrap(); } + + #[inline] + fn serialized_size(&self) -> usize { + 8 + } } impl Deserializable for bool { @@ -114,6 +149,12 @@ impl Serializable for String { .append(&CompactInteger::from(bytes.len())) .append_slice(bytes); } + + #[inline] + fn serialized_size(&self) -> usize { + let bytes: &[u8] = self.as_ref(); + CompactInteger::from(bytes.len()).serialized_size() + bytes.len() + } } impl Deserializable for String { @@ -129,6 +170,11 @@ macro_rules! impl_ser_for_hash { fn serialize(&self, stream: &mut Stream) { stream.append_slice(&**self); } + + #[inline] + fn serialized_size(&self) -> usize { + $size + } } impl Deserializable for $name { @@ -157,6 +203,11 @@ impl Serializable for Bytes { .append(&CompactInteger::from(self.len())) .append_slice(&self); } + + #[inline] + fn serialized_size(&self) -> usize { + CompactInteger::from(self.len()).serialized_size() + self.len() + } } impl Deserializable for Bytes { diff --git a/serialization/src/lib.rs b/serialization/src/lib.rs index 4c072d62..7dd80b52 100644 --- a/serialization/src/lib.rs +++ b/serialization/src/lib.rs @@ -10,4 +10,4 @@ pub use primitives::{hash, bytes}; pub use compact_integer::CompactInteger; pub use self::reader::{Reader, Deserializable, deserialize, Error}; -pub use self::stream::{Stream, Serializable, serialize}; +pub use self::stream::{Stream, Serializable, serialize, serialized_list_size}; diff --git a/serialization/src/stream.rs b/serialization/src/stream.rs index 89837064..9b124ef0 100644 --- a/serialization/src/stream.rs +++ b/serialization/src/stream.rs @@ -9,9 +9,20 @@ pub fn serialize(t: &Serializable) -> Bytes { stream.out() } +pub fn serialized_list_size(t: &[T]) -> usize where T: Serializable { + CompactInteger::from(t.len()).serialized_size() + + t.iter().map(Serializable::serialized_size).sum::() +} + pub trait Serializable { /// Serialize the struct and appends it to the end of stream. fn serialize(&self, s: &mut Stream); + + /// Hint about the size of serialized struct. + fn serialized_size(&self) -> usize where Self: Sized { + // fallback implementation + serialize(self).len() + } } /// Stream used for serialization. diff --git a/tools/graph.dot b/tools/graph.dot index e0806357..67c27066 100644 --- a/tools/graph.dot +++ b/tools/graph.dot @@ -4,199 +4,204 @@ digraph dependencies { N2[label="db",shape=box]; N3[label="keys",shape=box]; N4[label="message",shape=box]; - N5[label="p2p",shape=box]; - N6[label="script",shape=box]; - N7[label="ansi_term",shape=box]; - N8[label="arrayvec",shape=box]; - N9[label="nodrop",shape=box]; - N10[label="odds",shape=box]; - N11[label="base58",shape=box]; - N12[label="bitcrypto",shape=box]; - N13[label="primitives",shape=box]; - N14[label="rust-crypto",shape=box]; - N15[label="bitflags v0.4.0",shape=box]; - N16[label="bitflags v0.7.0",shape=box]; - N17[label="byteorder",shape=box]; - N18[label="cfg-if",shape=box]; - N19[label="chain",shape=box]; - N20[label="rustc-serialize",shape=box]; - N21[label="serialization",shape=box]; - N22[label="libc",shape=box]; - N23[label="strsim",shape=box]; - N24[label="term_size",shape=box]; - N25[label="unicode-segmentation",shape=box]; - N26[label="unicode-width",shape=box]; - N27[label="vec_map",shape=box]; - N28[label="yaml-rust",shape=box]; - N29[label="crossbeam",shape=box]; - N30[label="elastic-array",shape=box]; - N31[label="ethcore-devtools",shape=box]; - N32[label="parking_lot",shape=box]; - N33[label="rocksdb",shape=box]; - N34[label="deque",shape=box]; - N35[label="rand",shape=box]; - N36[label="eth-secp256k1",shape=box]; - N37[label="gcc",shape=box]; - N38[label="futures",shape=box]; - N39[label="log",shape=box]; - N40[label="futures-cpupool",shape=box]; - N41[label="num_cpus v1.1.0",shape=box]; - N42[label="rayon",shape=box]; - N43[label="kernel32-sys",shape=box]; - N44[label="winapi",shape=box]; - N45[label="winapi-build",shape=box]; - N46[label="lazy_static",shape=box]; - N47[label="lazycell",shape=box]; - N48[label="mio",shape=box]; - N49[label="miow",shape=box]; - N50[label="net2",shape=box]; - N51[label="nix",shape=box]; - N52[label="slab",shape=box]; - N53[label="ws2_32-sys",shape=box]; - N54[label="rustc_version",shape=box]; - N55[label="semver",shape=box]; - N56[label="void",shape=box]; - N57[label="num_cpus v0.2.13",shape=box]; - N58[label="owning_ref",shape=box]; - N59[label="time",shape=box]; - N60[label="tokio-core",shape=box]; - N61[label="parking_lot_core",shape=box]; - N62[label="smallvec",shape=box]; - N63[label="rocksdb-sys",shape=box]; - N64[label="scoped-tls",shape=box]; + N5[label="miner",shape=box]; + N6[label="p2p",shape=box]; + N7[label="script",shape=box]; + N8[label="ansi_term",shape=box]; + N9[label="arrayvec",shape=box]; + N10[label="nodrop",shape=box]; + N11[label="odds",shape=box]; + N12[label="base58",shape=box]; + N13[label="bitcrypto",shape=box]; + N14[label="primitives",shape=box]; + N15[label="rust-crypto",shape=box]; + N16[label="bitflags v0.4.0",shape=box]; + N17[label="bitflags v0.7.0",shape=box]; + N18[label="byteorder",shape=box]; + N19[label="cfg-if",shape=box]; + N20[label="chain",shape=box]; + N21[label="rustc-serialize",shape=box]; + N22[label="serialization",shape=box]; + N23[label="libc",shape=box]; + N24[label="strsim",shape=box]; + N25[label="term_size",shape=box]; + N26[label="unicode-segmentation",shape=box]; + N27[label="unicode-width",shape=box]; + N28[label="vec_map",shape=box]; + N29[label="yaml-rust",shape=box]; + N30[label="crossbeam",shape=box]; + N31[label="elastic-array",shape=box]; + N32[label="ethcore-devtools",shape=box]; + N33[label="parking_lot",shape=box]; + N34[label="rocksdb",shape=box]; + N35[label="deque",shape=box]; + N36[label="rand",shape=box]; + N37[label="eth-secp256k1",shape=box]; + N38[label="gcc",shape=box]; + N39[label="futures",shape=box]; + N40[label="log",shape=box]; + N41[label="futures-cpupool",shape=box]; + N42[label="num_cpus v1.1.0",shape=box]; + N43[label="rayon",shape=box]; + N44[label="kernel32-sys",shape=box]; + N45[label="winapi",shape=box]; + N46[label="winapi-build",shape=box]; + N47[label="lazy_static",shape=box]; + N48[label="lazycell",shape=box]; + N49[label="mio",shape=box]; + N50[label="miow",shape=box]; + N51[label="net2",shape=box]; + N52[label="nix",shape=box]; + N53[label="slab",shape=box]; + N54[label="ws2_32-sys",shape=box]; + N55[label="rustc_version",shape=box]; + N56[label="semver",shape=box]; + N57[label="void",shape=box]; + N58[label="num_cpus v0.2.13",shape=box]; + N59[label="owning_ref",shape=box]; + N60[label="time",shape=box]; + N61[label="tokio-core",shape=box]; + N62[label="parking_lot_core",shape=box]; + N63[label="smallvec",shape=box]; + N64[label="rocksdb-sys",shape=box]; + N65[label="scoped-tls",shape=box]; N0 -> N1[label="",style=dashed]; N0 -> N2[label="",style=dashed]; N0 -> N3[label="",style=dashed]; N0 -> N4[label="",style=dashed]; N0 -> N5[label="",style=dashed]; N0 -> N6[label="",style=dashed]; - N1 -> N7[label="",style=dashed]; - N1 -> N16[label="",style=dashed]; - N1 -> N22[label="",style=dashed]; + N0 -> N7[label="",style=dashed]; + N1 -> N8[label="",style=dashed]; + N1 -> N17[label="",style=dashed]; N1 -> N23[label="",style=dashed]; N1 -> N24[label="",style=dashed]; N1 -> N25[label="",style=dashed]; N1 -> N26[label="",style=dashed]; N1 -> N27[label="",style=dashed]; N1 -> N28[label="",style=dashed]; - N2 -> N13[label="",style=dashed]; - N2 -> N17[label="",style=dashed]; - N2 -> N19[label="",style=dashed]; - N2 -> N21[label="",style=dashed]; - N2 -> N30[label="",style=dashed]; + N1 -> N29[label="",style=dashed]; + N2 -> N14[label="",style=dashed]; + N2 -> N18[label="",style=dashed]; + N2 -> N20[label="",style=dashed]; + N2 -> N22[label="",style=dashed]; N2 -> N31[label="",style=dashed]; N2 -> N32[label="",style=dashed]; N2 -> N33[label="",style=dashed]; - N3 -> N11[label="",style=dashed]; + N2 -> N34[label="",style=dashed]; N3 -> N12[label="",style=dashed]; N3 -> N13[label="",style=dashed]; - N3 -> N20[label="",style=dashed]; - N3 -> N35[label="",style=dashed]; + N3 -> N14[label="",style=dashed]; + N3 -> N21[label="",style=dashed]; N3 -> N36[label="",style=dashed]; - N3 -> N46[label="",style=dashed]; - N4 -> N12[label="",style=dashed]; + N3 -> N37[label="",style=dashed]; + N3 -> N47[label="",style=dashed]; N4 -> N13[label="",style=dashed]; - N4 -> N17[label="",style=dashed]; - N4 -> N19[label="",style=dashed]; - N4 -> N21[label="",style=dashed]; - N5 -> N4[label="",style=dashed]; - N5 -> N12[label="",style=dashed]; - N5 -> N13[label="",style=dashed]; - N5 -> N32[label="",style=dashed]; - N5 -> N35[label="",style=dashed]; - N5 -> N38[label="",style=dashed]; - N5 -> N40[label="",style=dashed]; - N5 -> N59[label="",style=dashed]; - N5 -> N60[label="",style=dashed]; - N6 -> N3[label="",style=dashed]; - N6 -> N12[label="",style=dashed]; + N4 -> N14[label="",style=dashed]; + N4 -> N18[label="",style=dashed]; + N4 -> N20[label="",style=dashed]; + N4 -> N22[label="",style=dashed]; + N5 -> N14[label="",style=dashed]; + N5 -> N20[label="",style=dashed]; + N5 -> N22[label="",style=dashed]; + N6 -> N4[label="",style=dashed]; N6 -> N13[label="",style=dashed]; - N6 -> N19[label="",style=dashed]; - N6 -> N21[label="",style=dashed]; - N8 -> N9[label=""]; - N8 -> N10[label=""]; + N6 -> N14[label="",style=dashed]; + N6 -> N33[label="",style=dashed]; + N6 -> N36[label="",style=dashed]; + N6 -> N39[label="",style=dashed]; + N6 -> N41[label="",style=dashed]; + N6 -> N60[label="",style=dashed]; + N6 -> N61[label="",style=dashed]; + N7 -> N3[label="",style=dashed]; + N7 -> N13[label="",style=dashed]; + N7 -> N14[label="",style=dashed]; + N7 -> N20[label="",style=dashed]; + N7 -> N22[label="",style=dashed]; N9 -> N10[label=""]; - N12 -> N13[label="",style=dashed]; - N12 -> N14[label="",style=dashed]; - N13 -> N20[label="",style=dashed]; - N14 -> N20[label="",style=dashed]; - N14 -> N22[label="",style=dashed]; - N14 -> N35[label="",style=dashed]; - N14 -> N37[label="",style=dashed]; - N14 -> N59[label="",style=dashed]; - N19 -> N12[label="",style=dashed]; - N19 -> N13[label="",style=dashed]; - N19 -> N20[label="",style=dashed]; - N19 -> N21[label="",style=dashed]; - N21 -> N13[label="",style=dashed]; - N21 -> N17[label="",style=dashed]; - N24 -> N22[label="",style=dashed]; - N24 -> N43[label="",style=dashed]; - N24 -> N44[label="",style=dashed]; - N31 -> N35[label="",style=dashed]; - N32 -> N58[label="",style=dashed]; - N32 -> N61[label="",style=dashed]; - N33 -> N22[label="",style=dashed]; - N33 -> N63[label="",style=dashed]; - N34 -> N35[label="",style=dashed]; - N35 -> N22[label="",style=dashed]; - N36 -> N8[label="",style=dashed]; - N36 -> N20[label="",style=dashed]; - N36 -> N22[label="",style=dashed]; - N36 -> N35[label="",style=dashed]; - N36 -> N37[label="",style=dashed]; - N37 -> N42[label="",style=dashed]; - N38 -> N39[label="",style=dashed]; - N40 -> N29[label="",style=dashed]; - N40 -> N38[label="",style=dashed]; - N40 -> N41[label="",style=dashed]; - N41 -> N22[label="",style=dashed]; - N42 -> N34[label="",style=dashed]; - N42 -> N35[label="",style=dashed]; - N42 -> N57[label="",style=dashed]; - N43 -> N44[label="",style=dashed]; - N43 -> N45[label="",style=dashed]; - N48 -> N22[label="",style=dashed]; - N48 -> N39[label="",style=dashed]; - N48 -> N43[label="",style=dashed]; - N48 -> N44[label="",style=dashed]; - N48 -> N47[label=""]; - N48 -> N49[label=""]; - N48 -> N50[label=""]; - N48 -> N51[label=""]; - N48 -> N52[label="",style=dashed]; - N49 -> N43[label=""]; - N49 -> N44[label=""]; + N9 -> N11[label=""]; + N10 -> N11[label=""]; + N13 -> N14[label="",style=dashed]; + N13 -> N15[label="",style=dashed]; + N14 -> N21[label="",style=dashed]; + N15 -> N21[label="",style=dashed]; + N15 -> N23[label="",style=dashed]; + N15 -> N36[label="",style=dashed]; + N15 -> N38[label="",style=dashed]; + N15 -> N60[label="",style=dashed]; + N20 -> N13[label="",style=dashed]; + N20 -> N14[label="",style=dashed]; + N20 -> N21[label="",style=dashed]; + N20 -> N22[label="",style=dashed]; + N22 -> N14[label="",style=dashed]; + N22 -> N18[label="",style=dashed]; + N25 -> N23[label="",style=dashed]; + N25 -> N44[label="",style=dashed]; + N25 -> N45[label="",style=dashed]; + N32 -> N36[label="",style=dashed]; + N33 -> N59[label="",style=dashed]; + N33 -> N62[label="",style=dashed]; + N34 -> N23[label="",style=dashed]; + N34 -> N64[label="",style=dashed]; + N35 -> N36[label="",style=dashed]; + N36 -> N23[label="",style=dashed]; + N37 -> N9[label="",style=dashed]; + N37 -> N21[label="",style=dashed]; + N37 -> N23[label="",style=dashed]; + N37 -> N36[label="",style=dashed]; + N37 -> N38[label="",style=dashed]; + N38 -> N43[label="",style=dashed]; + N39 -> N40[label="",style=dashed]; + N41 -> N30[label="",style=dashed]; + N41 -> N39[label="",style=dashed]; + N41 -> N42[label="",style=dashed]; + N42 -> N23[label="",style=dashed]; + N43 -> N35[label="",style=dashed]; + N43 -> N36[label="",style=dashed]; + N43 -> N58[label="",style=dashed]; + N44 -> N45[label="",style=dashed]; + N44 -> N46[label="",style=dashed]; + N49 -> N23[label="",style=dashed]; + N49 -> N40[label="",style=dashed]; + N49 -> N44[label="",style=dashed]; + N49 -> N45[label="",style=dashed]; + N49 -> N48[label=""]; N49 -> N50[label=""]; - N49 -> N53[label=""]; - N50 -> N18[label=""]; - N50 -> N22[label=""]; - N50 -> N43[label=""]; + N49 -> N51[label=""]; + N49 -> N52[label=""]; + N49 -> N53[label="",style=dashed]; N50 -> N44[label=""]; - N50 -> N53[label=""]; - N51 -> N15[label=""]; - N51 -> N18[label=""]; - N51 -> N22[label=""]; + N50 -> N45[label=""]; + N50 -> N51[label=""]; + N50 -> N54[label=""]; + N51 -> N19[label=""]; + N51 -> N23[label=""]; + N51 -> N44[label=""]; + N51 -> N45[label=""]; N51 -> N54[label=""]; - N51 -> N55[label=""]; - N51 -> N56[label=""]; - N53 -> N44[label=""]; - N53 -> N45[label=""]; - N54 -> N55[label=""]; - N57 -> N22[label="",style=dashed]; - N59 -> N22[label="",style=dashed]; - N59 -> N43[label="",style=dashed]; - N59 -> N44[label="",style=dashed]; - N60 -> N38[label="",style=dashed]; - N60 -> N39[label="",style=dashed]; - N60 -> N48[label="",style=dashed]; - N60 -> N52[label="",style=dashed]; - N60 -> N64[label="",style=dashed]; - N61 -> N22[label="",style=dashed]; - N61 -> N35[label="",style=dashed]; - N61 -> N43[label="",style=dashed]; - N61 -> N44[label="",style=dashed]; - N61 -> N62[label="",style=dashed]; - N63 -> N22[label="",style=dashed]; - N63 -> N37[label="",style=dashed]; + N52 -> N16[label=""]; + N52 -> N19[label=""]; + N52 -> N23[label=""]; + N52 -> N55[label=""]; + N52 -> N56[label=""]; + N52 -> N57[label=""]; + N54 -> N45[label=""]; + N54 -> N46[label=""]; + N55 -> N56[label=""]; + N58 -> N23[label="",style=dashed]; + N60 -> N23[label="",style=dashed]; + N60 -> N44[label="",style=dashed]; + N60 -> N45[label="",style=dashed]; + N61 -> N39[label="",style=dashed]; + N61 -> N40[label="",style=dashed]; + N61 -> N49[label="",style=dashed]; + N61 -> N53[label="",style=dashed]; + N61 -> N65[label="",style=dashed]; + N62 -> N23[label="",style=dashed]; + N62 -> N36[label="",style=dashed]; + N62 -> N44[label="",style=dashed]; + N62 -> N45[label="",style=dashed]; + N62 -> N63[label="",style=dashed]; + N64 -> N23[label="",style=dashed]; + N64 -> N38[label="",style=dashed]; } diff --git a/tools/graph.png b/tools/graph.png index 8e005b72..0339e1e6 100644 Binary files a/tools/graph.png and b/tools/graph.png differ diff --git a/tools/test.sh b/tools/test.sh index bff315a1..1e433887 100755 --- a/tools/test.sh +++ b/tools/test.sh @@ -7,6 +7,7 @@ cargo test\ -p bitcrypto\ -p keys\ -p message\ + -p miner\ -p p2p\ -p primitives\ -p script\