Merge branch 'master' into mempool
This commit is contained in:
commit
e3968cfa46
|
@ -12,3 +12,6 @@
|
|||
|
||||
# GitEye stuff
|
||||
/.project
|
||||
|
||||
# idea ide
|
||||
.idea
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
name = "pbtc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitcrypto 0.1.0",
|
||||
"clap 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"db 0.1.0",
|
||||
"keys 0.1.0",
|
||||
"message 0.1.0",
|
||||
"miner 0.1.0",
|
||||
|
@ -89,6 +89,33 @@ name = "crossbeam"
|
|||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "db"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chain 0.1.0",
|
||||
"elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-devtools 1.3.0",
|
||||
"parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"primitives 0.1.0",
|
||||
"rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)",
|
||||
"serialization 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deque"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elastic-array"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "eth-secp256k1"
|
||||
version = "0.5.6"
|
||||
|
@ -101,6 +128,13 @@ dependencies = [
|
|||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethcore-devtools"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.2"
|
||||
|
@ -123,6 +157,9 @@ dependencies = [
|
|||
name = "gcc"
|
||||
version = "0.3.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
|
@ -246,6 +283,14 @@ dependencies = [
|
|||
"odds 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.1.0"
|
||||
|
@ -316,6 +361,34 @@ dependencies = [
|
|||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocksdb"
|
||||
version = "0.4.5"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocksdb-sys"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-crypto"
|
||||
version = "0.2.36"
|
||||
|
@ -471,6 +544,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
|
||||
"checksum clap 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5fa304b03c49ccbb005784fc26e985b5d2310b1d37f2c311ce90dbcd18ea5fde"
|
||||
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
|
||||
"checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
|
||||
"checksum elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bc9250a632e7c001b741eb0ec6cee93c9a5b6d5f1879696a4b94d62b012210a"
|
||||
"checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
|
||||
"checksum futures 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0bd34f72c0fffc9d2f6c570fd392bf99b9c5cd1481d79809e1cc2320befc0af0"
|
||||
"checksum futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bb982bb25cd8fa5da6a8eb3a460354c984ff1113da82bcb4f0b0862b5795db82"
|
||||
|
@ -485,12 +560,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2"
|
||||
"checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2"
|
||||
"checksum nodrop 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbadd3f4c98dea0bd3d9b4be4c0cdaf1ab57035cb2e41fce3983db5add7cc5"
|
||||
"checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3"
|
||||
"checksum num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8890e6084723d57d0df8d2720b0d60c6ee67d6c93e7169630e4371e88765dcad"
|
||||
"checksum odds 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "308eb922caa165a2c4be8004abf87822fc4b444ab339f70fc172236b1905efb0"
|
||||
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
||||
"checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125"
|
||||
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
||||
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
|
||||
"checksum rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "655df67c314c30fa3055a365eae276eb88aa4f3413a352a1ab32c1320eda41ea"
|
||||
"checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
||||
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||
|
|
|
@ -7,13 +7,12 @@ description = "Parity bitcoin client."
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2", features = ["yaml"] }
|
||||
|
||||
bitcrypto = { path = "crypto" }
|
||||
keys = { path = "keys" }
|
||||
message = { path = "message" }
|
||||
miner = { path = "miner" }
|
||||
p2p = { path = "p2p" }
|
||||
script = { path = "script" }
|
||||
db = { path = "db" }
|
||||
|
||||
[[bin]]
|
||||
path = "pbtc/main.rs"
|
||||
|
|
|
@ -40,6 +40,10 @@ impl From<&'static str> for Block {
|
|||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(header: BlockHeader, transactions: Vec<Transaction>) -> Self {
|
||||
Block { block_header: header, transactions: transactions }
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> H256 {
|
||||
dhash256(&serialize(&self.block_header))
|
||||
}
|
||||
|
@ -49,6 +53,14 @@ impl Block {
|
|||
let hashes = self.transactions.iter().map(Transaction::hash).collect::<Vec<H256>>();
|
||||
merkle_root(&hashes)
|
||||
}
|
||||
|
||||
pub fn transactions(&self) -> &[Transaction] {
|
||||
&self.transactions
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &BlockHeader {
|
||||
&self.block_header
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "db"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Volf <nikvolf@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
elastic-array = "0.5"
|
||||
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
primitives = { path = "../primitives" }
|
||||
byteorder = "0.5"
|
||||
chain = { path = "../chain" }
|
||||
serialization = { path = "../serialization" }
|
||||
parking_lot = "0.3"
|
|
@ -0,0 +1,510 @@
|
|||
//! Key-Value store abstraction with `RocksDB` backend.
|
||||
|
||||
use std::{self, fs, mem};
|
||||
use std::io::ErrorKind;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use rocksdb::{DB, Writable, WriteBatch, WriteOptions, IteratorMode, DBIterator,
|
||||
Options, DBCompactionStyle, BlockBasedOptions, Cache, Column};
|
||||
use elastic_array::ElasticArray32;
|
||||
use parking_lot::RwLock;
|
||||
use primitives::bytes::Bytes;
|
||||
|
||||
/// Database error
|
||||
pub enum Error {
|
||||
/// Rocksdb error
|
||||
DB(String),
|
||||
/// Io error
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(err: String) -> Error {
|
||||
Error::DB(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
const DB_BACKGROUND_FLUSHES: i32 = 2;
|
||||
const DB_BACKGROUND_COMPACTIONS: i32 = 2;
|
||||
|
||||
/// Write transaction. Batches a sequence of put/delete operations for efficiency.
|
||||
pub struct DBTransaction {
|
||||
ops: Vec<DBOp>,
|
||||
}
|
||||
|
||||
enum DBOp {
|
||||
Insert {
|
||||
col: Option<u32>,
|
||||
key: ElasticArray32<u8>,
|
||||
value: Bytes,
|
||||
},
|
||||
Delete {
|
||||
col: Option<u32>,
|
||||
key: ElasticArray32<u8>,
|
||||
}
|
||||
}
|
||||
|
||||
impl DBTransaction {
|
||||
/// Create new transaction.
|
||||
pub fn new(_db: &Database) -> DBTransaction {
|
||||
DBTransaction {
|
||||
ops: Vec::with_capacity(256),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
pub fn put(&mut self, col: Option<u32>, key: &[u8], value: &[u8]) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
ekey.append_slice(key);
|
||||
self.ops.push(DBOp::Insert {
|
||||
col: col,
|
||||
key: ekey,
|
||||
value: value.to_vec().into(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
pub fn put_vec(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
ekey.append_slice(key);
|
||||
self.ops.push(DBOp::Insert {
|
||||
col: col,
|
||||
key: ekey,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete value by key.
|
||||
pub fn delete(&mut self, col: Option<u32>, key: &[u8]) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
ekey.append_slice(key);
|
||||
self.ops.push(DBOp::Delete {
|
||||
col: col,
|
||||
key: ekey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum KeyState {
|
||||
Insert(Bytes),
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Compaction profile for the database settings
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CompactionProfile {
|
||||
/// L0-L1 target file size
|
||||
pub initial_file_size: u64,
|
||||
/// L2-LN target file size multiplier
|
||||
pub file_size_multiplier: i32,
|
||||
/// rate limiter for background flushes and compactions, bytes/sec, if any
|
||||
pub write_rate_limit: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for CompactionProfile {
|
||||
/// Default profile suitable for most storage
|
||||
fn default() -> CompactionProfile {
|
||||
CompactionProfile {
|
||||
initial_file_size: 32 * 1024 * 1024,
|
||||
file_size_multiplier: 2,
|
||||
write_rate_limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactionProfile {
|
||||
/// Slow hdd compaction profile
|
||||
pub fn hdd() -> CompactionProfile {
|
||||
CompactionProfile {
|
||||
initial_file_size: 192 * 1024 * 1024,
|
||||
file_size_multiplier: 1,
|
||||
write_rate_limit: Some(8 * 1024 * 1024),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database configuration
|
||||
#[derive(Clone)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Max number of open files.
|
||||
pub max_open_files: i32,
|
||||
/// Cache sizes (in MiB) for specific columns.
|
||||
pub cache_sizes: HashMap<Option<u32>, usize>,
|
||||
/// Compaction profile
|
||||
pub compaction: CompactionProfile,
|
||||
/// Set number of columns
|
||||
pub columns: Option<u32>,
|
||||
/// Should we keep WAL enabled?
|
||||
pub wal: bool,
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
/// Create new `DatabaseConfig` with default parameters and specified set of columns.
|
||||
/// Note that cache sizes must be explicitly set.
|
||||
pub fn with_columns(columns: Option<u32>) -> Self {
|
||||
let mut config = Self::default();
|
||||
config.columns = columns;
|
||||
config
|
||||
}
|
||||
|
||||
/// Set the column cache size in MiB.
|
||||
pub fn set_cache(&mut self, col: Option<u32>, size: usize) {
|
||||
self.cache_sizes.insert(col, size);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
cache_sizes: HashMap::new(),
|
||||
max_open_files: 512,
|
||||
compaction: CompactionProfile::default(),
|
||||
columns: None,
|
||||
wal: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database iterator for flushed data only
|
||||
pub struct DatabaseIterator {
|
||||
iter: DBIterator,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DatabaseIterator {
|
||||
type Item = (Box<[u8]>, Box<[u8]>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next()
|
||||
}
|
||||
}
|
||||
|
||||
struct DBAndColumns {
|
||||
db: DB,
|
||||
cfs: Vec<Column>,
|
||||
}
|
||||
|
||||
/// Key-Value database.
|
||||
pub struct Database {
|
||||
db: RwLock<Option<DBAndColumns>>,
|
||||
config: DatabaseConfig,
|
||||
write_opts: WriteOptions,
|
||||
overlay: RwLock<Vec<HashMap<ElasticArray32<u8>, KeyState>>>,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Open database with default settings.
|
||||
pub fn open_default(path: &str) -> Result<Database, String> {
|
||||
Database::open(&DatabaseConfig::default(), path)
|
||||
}
|
||||
|
||||
/// Open database file. Creates if it does not exist.
|
||||
pub fn open(config: &DatabaseConfig, path: &str) -> Result<Database, String> {
|
||||
// default cache size for columns not specified.
|
||||
const DEFAULT_CACHE: usize = 2;
|
||||
|
||||
let mut opts = Options::new();
|
||||
if let Some(rate_limit) = config.compaction.write_rate_limit {
|
||||
try!(opts.set_parsed_options(&format!("rate_limiter_bytes_per_sec={}", rate_limit)));
|
||||
}
|
||||
try!(opts.set_parsed_options(&format!("max_total_wal_size={}", 64 * 1024 * 1024)));
|
||||
opts.set_max_open_files(config.max_open_files);
|
||||
opts.create_if_missing(true);
|
||||
opts.set_use_fsync(false);
|
||||
|
||||
opts.set_max_background_flushes(DB_BACKGROUND_FLUSHES);
|
||||
opts.set_max_background_compactions(DB_BACKGROUND_COMPACTIONS);
|
||||
|
||||
// compaction settings
|
||||
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
|
||||
opts.set_target_file_size_base(config.compaction.initial_file_size);
|
||||
opts.set_target_file_size_multiplier(config.compaction.file_size_multiplier);
|
||||
|
||||
let mut cf_options = Vec::with_capacity(config.columns.unwrap_or(0) as usize);
|
||||
|
||||
for col in 0 .. config.columns.unwrap_or(0) {
|
||||
let mut opts = Options::new();
|
||||
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
|
||||
opts.set_target_file_size_base(config.compaction.initial_file_size);
|
||||
opts.set_target_file_size_multiplier(config.compaction.file_size_multiplier);
|
||||
|
||||
let col_opt = config.columns.map(|_| col);
|
||||
|
||||
{
|
||||
let cache_size = config.cache_sizes.get(&col_opt).cloned().unwrap_or(DEFAULT_CACHE);
|
||||
let mut block_opts = BlockBasedOptions::new();
|
||||
// all goes to read cache.
|
||||
block_opts.set_cache(Cache::new(cache_size * 1024 * 1024));
|
||||
opts.set_block_based_table_factory(&block_opts);
|
||||
}
|
||||
|
||||
cf_options.push(opts);
|
||||
}
|
||||
|
||||
let mut write_opts = WriteOptions::new();
|
||||
if !config.wal {
|
||||
write_opts.disable_wal(true);
|
||||
}
|
||||
|
||||
let mut cfs: Vec<Column> = Vec::new();
|
||||
let db = match config.columns {
|
||||
Some(columns) => {
|
||||
let cfnames: Vec<_> = (0..columns).map(|c| format!("col{}", c)).collect();
|
||||
let cfnames: Vec<&str> = cfnames.iter().map(|n| n as &str).collect();
|
||||
match DB::open_cf(&opts, path, &cfnames, &cf_options) {
|
||||
Ok(db) => {
|
||||
cfs = cfnames.iter().map(|n| db.cf_handle(n).unwrap()).collect();
|
||||
assert!(cfs.len() == columns as usize);
|
||||
Ok(db)
|
||||
}
|
||||
Err(_) => {
|
||||
// retry and create CFs
|
||||
match DB::open_cf(&opts, path, &[], &[]) {
|
||||
Ok(mut db) => {
|
||||
cfs = cfnames.iter().enumerate().map(|(i, n)| db.create_cf(n, &cf_options[i]).unwrap()).collect();
|
||||
Ok(db)
|
||||
},
|
||||
err @ Err(_) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => DB::open(&opts, path)
|
||||
};
|
||||
let db = match db {
|
||||
Ok(db) => db,
|
||||
Err(ref s) if s.starts_with("Corruption:") => {
|
||||
try!(DB::repair(&opts, path));
|
||||
try!(DB::open(&opts, path))
|
||||
},
|
||||
Err(s) => { return Err(s); }
|
||||
};
|
||||
let num_cols = cfs.len();
|
||||
Ok(Database {
|
||||
db: RwLock::new(Some(DBAndColumns{ db: db, cfs: cfs })),
|
||||
config: config.clone(),
|
||||
write_opts: write_opts,
|
||||
overlay: RwLock::new((0..(num_cols + 1)).map(|_| HashMap::new()).collect()),
|
||||
path: path.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates new transaction for this database.
|
||||
pub fn transaction(&self) -> DBTransaction {
|
||||
DBTransaction::new(self)
|
||||
}
|
||||
|
||||
|
||||
fn to_overlay_column(col: Option<u32>) -> usize {
|
||||
col.map_or(0, |c| (c + 1) as usize)
|
||||
}
|
||||
|
||||
/// Commit transaction to database.
|
||||
pub fn write_buffered(&self, tr: DBTransaction) {
|
||||
let mut overlay = self.overlay.write();
|
||||
let ops = tr.ops;
|
||||
for op in ops {
|
||||
match op {
|
||||
DBOp::Insert { col, key, value } => {
|
||||
let c = Self::to_overlay_column(col);
|
||||
overlay[c].insert(key, KeyState::Insert(value));
|
||||
},
|
||||
DBOp::Delete { col, key } => {
|
||||
let c = Self::to_overlay_column(col);
|
||||
overlay[c].insert(key, KeyState::Delete);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Commit buffered changes to database.
|
||||
pub fn flush(&self) -> Result<(), String> {
|
||||
match *self.db.read() {
|
||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||
let batch = WriteBatch::new();
|
||||
let mut overlay = self.overlay.write();
|
||||
|
||||
for (c, column) in overlay.iter_mut().enumerate() {
|
||||
let column_data = mem::replace(column, HashMap::new());
|
||||
for (key, state) in column_data.into_iter() {
|
||||
match state {
|
||||
KeyState::Delete => {
|
||||
if c > 0 {
|
||||
try!(batch.delete_cf(cfs[c - 1], &key));
|
||||
} else {
|
||||
try!(batch.delete(&key));
|
||||
}
|
||||
},
|
||||
KeyState::Insert(value) => {
|
||||
if c > 0 {
|
||||
try!(batch.put_cf(cfs[c - 1], &key, &value));
|
||||
} else {
|
||||
try!(batch.put(&key, &value));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
db.write_opt(batch, &self.write_opts)
|
||||
},
|
||||
None => Err("Database is closed".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit transaction to database.
|
||||
pub fn write(&self, tr: DBTransaction) -> Result<(), String> {
|
||||
match *self.db.read() {
|
||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||
let batch = WriteBatch::new();
|
||||
let ops = tr.ops;
|
||||
for op in ops {
|
||||
match op {
|
||||
DBOp::Insert { col, key, value } => {
|
||||
try!(col.map_or_else(|| batch.put(&key, &value), |c| batch.put_cf(cfs[c as usize], &key, &value)))
|
||||
},
|
||||
DBOp::Delete { col, key } => {
|
||||
try!(col.map_or_else(|| batch.delete(&key), |c| batch.delete_cf(cfs[c as usize], &key)))
|
||||
},
|
||||
}
|
||||
}
|
||||
db.write_opt(batch, &self.write_opts)
|
||||
},
|
||||
None => Err("Database is closed".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get value by key.
|
||||
pub fn get(&self, col: Option<u32>, key: &[u8]) -> Result<Option<Bytes>, String> {
|
||||
match *self.db.read() {
|
||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||
let overlay = &self.overlay.read()[Self::to_overlay_column(col)];
|
||||
match overlay.get(key) {
|
||||
Some(&KeyState::Insert(ref value)) => Ok(Some(value.clone())),
|
||||
Some(&KeyState::Delete) => Ok(None),
|
||||
None => {
|
||||
col.map_or_else(
|
||||
|| db.get(key).map(|r| r.map(|v| v.to_vec().into())),
|
||||
|c| db.get_cf(cfs[c as usize], key).map(|r| r.map(|v| v.to_vec().into())))
|
||||
},
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get database iterator for flushed data.
|
||||
pub fn iter(&self, col: Option<u32>) -> DatabaseIterator {
|
||||
//TODO: iterate over overlay
|
||||
match *self.db.read() {
|
||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||
col.map_or_else(|| DatabaseIterator { iter: db.iterator(IteratorMode::Start) },
|
||||
|c| DatabaseIterator { iter: db.iterator_cf(cfs[c as usize], IteratorMode::Start).unwrap() })
|
||||
},
|
||||
None => panic!("Not supported yet") //TODO: return an empty iterator or change return type
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the database
|
||||
fn close(&self) {
|
||||
*self.db.write() = None;
|
||||
self.overlay.write().clear();
|
||||
}
|
||||
|
||||
/// Restore the database from a copy at given path.
|
||||
pub fn restore(&self, new_db: &str) -> Result<(), Error> {
|
||||
self.close();
|
||||
|
||||
let mut backup_db = PathBuf::from(&self.path);
|
||||
backup_db.pop();
|
||||
backup_db.push("backup_db");
|
||||
|
||||
let existed = match fs::rename(&self.path, &backup_db) {
|
||||
Ok(_) => true,
|
||||
Err(e) => if let ErrorKind::NotFound = e.kind() {
|
||||
false
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
match fs::rename(&new_db, &self.path) {
|
||||
Ok(_) => {
|
||||
// clean up the backup.
|
||||
if existed {
|
||||
try!(fs::remove_dir_all(&backup_db));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// restore the backup.
|
||||
if existed {
|
||||
try!(fs::rename(&backup_db, &self.path));
|
||||
}
|
||||
return Err(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
// reopen the database and steal handles into self
|
||||
let db = try!(Self::open(&self.config, &self.path));
|
||||
*self.db.write() = mem::replace(&mut *db.db.write(), None);
|
||||
*self.overlay.write() = mem::replace(&mut *db.overlay.write(), Vec::new());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use devtools::*;
|
||||
|
||||
fn test_db(config: &DatabaseConfig) {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let db = Database::open(config, path.as_path().to_str().unwrap()).unwrap();
|
||||
|
||||
let key1 = b"key1";
|
||||
let key2 = b"key2";
|
||||
let key3 = b"key3";
|
||||
|
||||
let mut batch = db.transaction();
|
||||
batch.put(None, key1, b"cat");
|
||||
batch.put(None, key2, b"dog");
|
||||
db.write(batch).unwrap();
|
||||
|
||||
assert_eq!(&*db.get(None, key1).unwrap().unwrap(), b"cat");
|
||||
|
||||
let contents: Vec<_> = db.iter(None).collect();
|
||||
assert_eq!(contents.len(), 2);
|
||||
assert_eq!(&*contents[0].0, &*key1);
|
||||
assert_eq!(&*contents[0].1, b"cat");
|
||||
assert_eq!(&*contents[1].0, &*key2);
|
||||
assert_eq!(&*contents[1].1, b"dog");
|
||||
|
||||
let mut batch = db.transaction();
|
||||
batch.delete(None, key1);
|
||||
db.write(batch).unwrap();
|
||||
|
||||
assert!(db.get(None, key1).unwrap().is_none());
|
||||
|
||||
let mut batch = db.transaction();
|
||||
batch.put(None, key1, b"cat");
|
||||
db.write(batch).unwrap();
|
||||
|
||||
let mut transaction = db.transaction();
|
||||
transaction.put(None, key3, b"elephant");
|
||||
transaction.delete(None, key1);
|
||||
db.write(transaction).unwrap();
|
||||
assert_eq!(&*db.get(None, key3).unwrap().unwrap(), b"elephant");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kvdb() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let _ = Database::open_default(path.as_path().to_str().unwrap()).unwrap();
|
||||
test_db(&DatabaseConfig::default());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//! Bitcoin database
|
||||
|
||||
extern crate elastic_array;
|
||||
extern crate rocksdb;
|
||||
extern crate parking_lot;
|
||||
extern crate primitives;
|
||||
extern crate byteorder;
|
||||
extern crate chain;
|
||||
extern crate serialization;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
|
||||
mod kvdb;
|
||||
mod storage;
|
||||
|
||||
pub enum BlockRef {
|
||||
Number(u64),
|
||||
Hash(primitives::hash::H256),
|
||||
}
|
||||
|
||||
pub use storage::{Storage, Store};
|
||||
pub use kvdb::Database;
|
|
@ -0,0 +1,300 @@
|
|||
//! Bitcoin storage
|
||||
|
||||
use std::{self, fs};
|
||||
use std::path::Path;
|
||||
use kvdb::{Database, DatabaseConfig};
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
use primitives::hash::H256;
|
||||
use primitives::bytes::Bytes;
|
||||
use super::BlockRef;
|
||||
use {chain, serialization};
|
||||
|
||||
const COL_COUNT: u32 = 10;
|
||||
const COL_META: u32 = 0;
|
||||
const COL_BLOCK_HASHES: u32 = 1;
|
||||
const COL_BLOCK_HEADERS: u32 = 2;
|
||||
const COL_BLOCK_TRANSACTIONS: u32 = 3;
|
||||
const COL_TRANSACTIONS: u32 = 4;
|
||||
const _COL_RESERVED1: u32 = 5;
|
||||
const _COL_RESERVED2: u32 = 6;
|
||||
const _COL_RESERVED3: u32 = 7;
|
||||
const _COL_RESERVED4: u32 = 8;
|
||||
const _COL_RESERVED5: u32 = 9;
|
||||
const _COL_RESERVED6: u32 = 10;
|
||||
|
||||
const DB_VERSION: u32 = 1;
|
||||
|
||||
/// Blockchain storage interface
|
||||
pub trait Store {
|
||||
/// resolves hash by block number
|
||||
fn block_hash(&self, number: u64) -> Option<H256>;
|
||||
|
||||
/// resolves header bytes by block reference (number/hash)
|
||||
fn block_header_bytes(&self, block_ref: BlockRef) -> Option<Bytes>;
|
||||
|
||||
/// resolves list of block transactions by block reference (number/hash)
|
||||
fn block_transaction_hashes(&self, block_ref: BlockRef) -> Vec<H256>;
|
||||
|
||||
/// resolves transaction body bytes by transaction hash
|
||||
fn transaction_bytes(&self, hash: &H256) -> Option<Bytes>;
|
||||
|
||||
/// resolves serialized transaction info by transaction hash
|
||||
fn transaction(&self, hash: &H256) -> Option<chain::Transaction>;
|
||||
|
||||
/// returns all transactions in the block by block reference (number/hash)
|
||||
fn block_transactions(&self, block_ref: BlockRef) -> Vec<chain::Transaction>;
|
||||
|
||||
/// resolves deserialized block body by block reference (number/hash)
|
||||
fn block(&self, block_ref: BlockRef) -> Option<chain::Block>;
|
||||
|
||||
/// insert block in the storage
|
||||
fn insert_block(&self, block: &chain::Block) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Blockchain storage with rocksdb database
|
||||
pub struct Storage {
|
||||
database: Database,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MetaError {
|
||||
UnsupportedVersion,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Database error
|
||||
pub enum Error {
|
||||
/// Rocksdb error
|
||||
DB(String),
|
||||
/// Io error
|
||||
Io(std::io::Error),
|
||||
/// Invalid meta info
|
||||
Meta(MetaError),
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(err: String) -> Error {
|
||||
Error::DB(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
fn u64_key(num: u64) -> [u8; 8] {
|
||||
let mut result = [0u8; 8];
|
||||
LittleEndian::write_u64(&mut result, num);
|
||||
result
|
||||
}
|
||||
|
||||
const KEY_VERSION: &'static[u8] = b"version";
|
||||
|
||||
impl Storage {
|
||||
|
||||
/// new storage at the selected path
|
||||
/// if no directory exists, it will be created
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Result<Storage, Error> {
|
||||
try!(fs::create_dir_all(path.as_ref()));
|
||||
let cfg = DatabaseConfig::with_columns(Some(COL_COUNT));
|
||||
let db = try!(Database::open(&cfg, &*path.as_ref().to_string_lossy()));
|
||||
|
||||
match try!(db.get(Some(COL_META), KEY_VERSION)) {
|
||||
Some(val) => {
|
||||
let ver = LittleEndian::read_u32(&val);
|
||||
if ver == DB_VERSION {
|
||||
Ok(Storage { database: db, })
|
||||
}
|
||||
else {
|
||||
Err(Error::Meta(MetaError::UnsupportedVersion))
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let mut meta_transaction = db.transaction();
|
||||
let mut ver_val = [0u8; 4];
|
||||
LittleEndian::write_u32(&mut ver_val, DB_VERSION);
|
||||
meta_transaction.put(Some(COL_META), KEY_VERSION, &ver_val);
|
||||
try!(db.write(meta_transaction));
|
||||
Ok(Storage { database: db, })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// is invoked on database non-fatal query errors
|
||||
fn db_error(&self, msg: String) {
|
||||
println!("Low-level database error: {}", &msg);
|
||||
}
|
||||
|
||||
/// get the value of the key in the database
|
||||
/// if the key is not present, reports non-fatal error and returns nothing
|
||||
fn get(&self, col: u32, key: &[u8]) -> Option<Bytes> {
|
||||
let res = self.database.get(Some(col), key);
|
||||
match res {
|
||||
Err(msg) => {
|
||||
self.db_error(msg);
|
||||
None
|
||||
},
|
||||
Ok(val) => val.map(|v| v.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// resolves hash for the block reference (which can be referenced by number or
|
||||
/// by hash)
|
||||
fn resolve_hash(&self, block_ref: BlockRef) -> Option<H256> {
|
||||
match block_ref {
|
||||
BlockRef::Number(n) => self.block_hash(n),
|
||||
BlockRef::Hash(h) => Some(h),
|
||||
}
|
||||
}
|
||||
|
||||
/// loads block transaction list by the provided block hash
|
||||
fn block_transaction_hashes_by_hash(&self, h: &H256) -> Vec<H256> {
|
||||
self.get(COL_BLOCK_TRANSACTIONS, &**h)
|
||||
.unwrap_or(Vec::new().into())
|
||||
.chunks(H256::size())
|
||||
.map(H256::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn block_transactions_by_hash(&self, h: &H256) -> Vec<chain::Transaction> {
|
||||
self.block_transaction_hashes_by_hash(h)
|
||||
.into_iter()
|
||||
.filter_map(|tx_hash| {
|
||||
self.transaction_bytes(&tx_hash).and_then(|tx_bytes| {
|
||||
match serialization::deserialize::<chain::Transaction>(&tx_bytes) {
|
||||
Ok(tx) => Some(tx),
|
||||
Err(e) => {
|
||||
self.db_error(format!("Error deserializing transaction, possible db corruption ({:?})", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Store for Storage {
|
||||
fn block_hash(&self, number: u64) -> Option<H256> {
|
||||
self.get(COL_BLOCK_HASHES, &u64_key(number))
|
||||
.map(|val| H256::from(&**val))
|
||||
}
|
||||
|
||||
fn block_header_bytes(&self, block_ref: BlockRef) -> Option<Bytes> {
|
||||
self.resolve_hash(block_ref).and_then(|h| self.get(COL_BLOCK_HEADERS, &*h))
|
||||
}
|
||||
|
||||
fn block_transaction_hashes(&self, block_ref: BlockRef) -> Vec<H256> {
|
||||
self.resolve_hash(block_ref)
|
||||
.map(|h| self.block_transaction_hashes_by_hash(&h))
|
||||
.unwrap_or(Vec::new())
|
||||
}
|
||||
|
||||
fn block_transactions(&self, block_ref: BlockRef) -> Vec<chain::Transaction> {
|
||||
self.resolve_hash(block_ref)
|
||||
.map(|h| self.block_transactions_by_hash(&h))
|
||||
.unwrap_or(Vec::new())
|
||||
}
|
||||
|
||||
fn transaction_bytes(&self, hash: &H256) -> Option<Bytes> {
|
||||
self.get(COL_TRANSACTIONS, &**hash)
|
||||
}
|
||||
|
||||
fn block(&self, block_ref: BlockRef) -> Option<chain::Block> {
|
||||
self.resolve_hash(block_ref).and_then(|block_hash|
|
||||
self.get(COL_BLOCK_HEADERS, &*block_hash)
|
||||
.and_then(|header_bytes| {
|
||||
let transactions = self.block_transactions_by_hash(&block_hash);;
|
||||
let maybe_header = match serialization::deserialize::<chain::BlockHeader>(&header_bytes) {
|
||||
Ok(header) => Some(header),
|
||||
Err(e) => {
|
||||
self.db_error(format!("Error deserializing header, possible db corruption ({:?})", e));
|
||||
None
|
||||
}
|
||||
};
|
||||
maybe_header.map(|header| chain::Block::new(header, transactions))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn insert_block(&self, block: &chain::Block) -> Result<(), Error> {
|
||||
let mut transaction = self.database.transaction();
|
||||
|
||||
let tx_space = block.transactions().len() * 32;
|
||||
let block_hash = block.hash();
|
||||
let mut tx_refs = Vec::with_capacity(tx_space);
|
||||
for tx in block.transactions() {
|
||||
let tx_hash = tx.hash();
|
||||
tx_refs.extend(&*tx_hash);
|
||||
transaction.put(
|
||||
Some(COL_TRANSACTIONS),
|
||||
&*tx_hash,
|
||||
&serialization::serialize(tx),
|
||||
);
|
||||
}
|
||||
transaction.put(Some(COL_BLOCK_TRANSACTIONS), &*block_hash, &tx_refs);
|
||||
|
||||
transaction.put(
|
||||
Some(COL_BLOCK_HEADERS),
|
||||
&*block_hash,
|
||||
&serialization::serialize(block.header())
|
||||
);
|
||||
|
||||
try!(self.database.write(transaction));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transaction(&self, hash: &H256) -> Option<chain::Transaction> {
|
||||
self.transaction_bytes(hash).and_then(|tx_bytes| {
|
||||
serialization::deserialize(&tx_bytes).map_err(
|
||||
|e| self.db_error(format!("Error deserializing transaction, possible db corruption ({:?})", e))
|
||||
).ok()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::{Storage, Store};
|
||||
use devtools::RandomTempPath;
|
||||
use chain::Block;
|
||||
use super::super::BlockRef;
|
||||
|
||||
#[test]
|
||||
fn open_store() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
assert!(Storage::new(path.as_path()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_block() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let store = Storage::new(path.as_path()).unwrap();
|
||||
|
||||
let block: Block = "01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad27b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33a5914ce6ed5b1b01e32f570201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287eff855189f949c1c6e5f58b37c88231373d8a59809cbae83059cc6469d65c665ccfd1cfeb75c6e8e19413bba7fbff9bc762419a76d87b16086eac000000000100000001a6b97044d03da79c005b20ea9c0e1a6d9dc12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac00000000".into();
|
||||
store.insert_block(&block).unwrap();
|
||||
|
||||
let loaded_block = store.block(BlockRef::Hash(block.hash())).unwrap();
|
||||
assert_eq!(loaded_block.hash(), block.hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_transaction() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let store = Storage::new(path.as_path()).unwrap();
|
||||
|
||||
let block: Block = "01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad27b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33a5914ce6ed5b1b01e32f570201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287eff855189f949c1c6e5f58b37c88231373d8a59809cbae83059cc6469d65c665ccfd1cfeb75c6e8e19413bba7fbff9bc762419a76d87b16086eac000000000100000001a6b97044d03da79c005b20ea9c0e1a6d9dc12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac00000000".into();
|
||||
let tx1 = block.transactions()[0].hash();
|
||||
store.insert_block(&block).unwrap();
|
||||
|
||||
let loaded_transaction = store.transaction(&tx1).unwrap();
|
||||
assert_eq!(loaded_transaction.hash(), block.transactions()[0].hash());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
description = "Ethcore development/test/build tools"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-devtools"
|
||||
version = "1.3.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
|
||||
[features]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
test = true
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! dev-tools
|
||||
|
||||
|
||||
extern crate rand;
|
||||
|
||||
mod random_path;
|
||||
mod test_socket;
|
||||
mod stop_guard;
|
||||
|
||||
pub use random_path::*;
|
||||
pub use test_socket::*;
|
||||
pub use stop_guard::*;
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Random path
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, env, ops};
|
||||
use rand::random;
|
||||
|
||||
pub struct RandomTempPath {
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
pub fn random_filename() -> String {
|
||||
random_str(8)
|
||||
}
|
||||
|
||||
pub fn random_str(len: usize) -> String {
|
||||
(0..len).map(|_| ((random::<f32>() * 26.0) as u8 + 97) as char).collect()
|
||||
}
|
||||
|
||||
impl RandomTempPath {
|
||||
pub fn new() -> RandomTempPath {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(random_filename());
|
||||
RandomTempPath {
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_dir() -> RandomTempPath {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(random_filename());
|
||||
fs::create_dir_all(dir.as_path()).unwrap();
|
||||
RandomTempPath {
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.path.to_str().unwrap()
|
||||
}
|
||||
|
||||
pub fn new_in(&self, name: &str) -> String {
|
||||
let mut path = self.path.clone();
|
||||
path.push(name);
|
||||
path.to_str().unwrap().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandomTempPath {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = fs::remove_dir_all(self.as_path()) {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuardedTempResult<T> {
|
||||
pub result: Option<T>,
|
||||
pub _temp: RandomTempPath
|
||||
}
|
||||
|
||||
impl<T> GuardedTempResult<T> {
|
||||
pub fn reference(&self) -> &T {
|
||||
self.result.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn reference_mut(&mut self) -> &mut T {
|
||||
self.result.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> T {
|
||||
self.result.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for GuardedTempResult<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.result.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for GuardedTempResult<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.result.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creates_dir() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
assert!(fs::metadata(temp.as_path()).unwrap().is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroys_dir() {
|
||||
let path_buf = {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
assert!(fs::metadata(temp.as_path()).unwrap().is_dir());
|
||||
let path_buf = temp.as_path().to_path_buf();
|
||||
path_buf
|
||||
};
|
||||
|
||||
assert!(fs::metadata(&path_buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provides_random() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
assert!(temp.as_path().to_str().is_some());
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Stop guard mod
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
/// Stop guard that will set a stop flag on drop
|
||||
pub struct StopGuard {
|
||||
flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl StopGuard {
|
||||
/// Create a stop guard
|
||||
pub fn new() -> StopGuard {
|
||||
StopGuard {
|
||||
flag: Arc::new(AtomicBool::new(false))
|
||||
}
|
||||
}
|
||||
|
||||
/// Share stop guard between the threads
|
||||
pub fn share(&self) -> Arc<AtomicBool> {
|
||||
self.flag.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StopGuard {
|
||||
fn drop(&mut self) {
|
||||
self.flag.store(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::{Read, Write, Result};
|
||||
use std::cmp;
|
||||
|
||||
pub struct TestSocket {
|
||||
pub read_buffer: Vec<u8>,
|
||||
pub write_buffer: Vec<u8>,
|
||||
pub cursor: usize,
|
||||
pub buf_size: usize,
|
||||
}
|
||||
|
||||
impl Default for TestSocket {
|
||||
fn default() -> Self {
|
||||
TestSocket::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSocket {
|
||||
pub fn new() -> Self {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_buf(buf_size: usize) -> TestSocket {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: buf_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ready(data: Vec<u8>) -> TestSocket {
|
||||
TestSocket {
|
||||
read_buffer: data,
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for TestSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
let end_position = cmp::min(self.read_buffer.len(), self.cursor+buf.len());
|
||||
if self.cursor > end_position { return Ok(0) }
|
||||
let len = cmp::max(end_position - self.cursor, 0);
|
||||
match len {
|
||||
0 => Ok(0),
|
||||
_ => {
|
||||
for i in self.cursor..end_position {
|
||||
buf[i-self.cursor] = self.read_buffer[i];
|
||||
}
|
||||
self.cursor = end_position;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestSocket {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
if self.buf_size == 0 || buf.len() < self.buf_size {
|
||||
self.write_buffer.extend(buf.iter().cloned());
|
||||
Ok(buf.len())
|
||||
}
|
||||
else {
|
||||
self.write_buffer.extend(buf.iter().take(self.buf_size).cloned());
|
||||
Ok(self.buf_size)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
extern crate bitcrypto as crypto;
|
||||
extern crate keys;
|
||||
extern crate script;
|
||||
extern crate message;
|
||||
|
|
|
@ -33,6 +33,14 @@ macro_rules! impl_hash {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for $name {
|
||||
fn from(slc: &[u8]) -> Self {
|
||||
let mut inner = [0u8; $size];
|
||||
inner[..].clone_from_slice(&slc[0..$size]);
|
||||
$name(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for $name {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().unwrap()
|
||||
|
@ -112,6 +120,10 @@ macro_rules! impl_hash {
|
|||
result.reverse();
|
||||
result
|
||||
}
|
||||
|
||||
pub fn size() -> usize {
|
||||
$size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,11 @@ pub struct Stream {
|
|||
}
|
||||
|
||||
impl Stream {
|
||||
/// New stream
|
||||
pub fn new() -> Self {
|
||||
Stream { buffer: Vec::new() }
|
||||
}
|
||||
|
||||
/// Serializes the struct and appends it to the end of stream.
|
||||
pub fn append(&mut self, t: &Serializable) -> &mut Self {
|
||||
t.serialize(self);
|
||||
|
|
315
tools/graph.dot
315
tools/graph.dot
|
@ -1,7 +1,7 @@
|
|||
digraph dependencies {
|
||||
N0[label="pbtc",shape=box];
|
||||
N1[label="bitcrypto",shape=box];
|
||||
N2[label="clap",shape=box];
|
||||
N1[label="clap",shape=box];
|
||||
N2[label="db",shape=box];
|
||||
N3[label="keys",shape=box];
|
||||
N4[label="message",shape=box];
|
||||
N5[label="p2p",shape=box];
|
||||
|
@ -11,166 +11,193 @@ digraph dependencies {
|
|||
N9[label="nodrop",shape=box];
|
||||
N10[label="odds",shape=box];
|
||||
N11[label="base58",shape=box];
|
||||
N12[label="primitives",shape=box];
|
||||
N13[label="rust-crypto",shape=box];
|
||||
N14[label="bitflags v0.4.0",shape=box];
|
||||
N15[label="bitflags v0.7.0",shape=box];
|
||||
N16[label="byteorder",shape=box];
|
||||
N17[label="cfg-if",shape=box];
|
||||
N18[label="chain",shape=box];
|
||||
N19[label="rustc-serialize",shape=box];
|
||||
N20[label="serialization",shape=box];
|
||||
N21[label="libc",shape=box];
|
||||
N22[label="strsim",shape=box];
|
||||
N23[label="term_size",shape=box];
|
||||
N24[label="unicode-segmentation",shape=box];
|
||||
N25[label="unicode-width",shape=box];
|
||||
N26[label="vec_map",shape=box];
|
||||
N27[label="yaml-rust",shape=box];
|
||||
N28[label="crossbeam",shape=box];
|
||||
N29[label="eth-secp256k1",shape=box];
|
||||
N30[label="gcc",shape=box];
|
||||
N31[label="rand",shape=box];
|
||||
N32[label="futures",shape=box];
|
||||
N33[label="log",shape=box];
|
||||
N34[label="futures-cpupool",shape=box];
|
||||
N35[label="num_cpus",shape=box];
|
||||
N36[label="kernel32-sys",shape=box];
|
||||
N37[label="winapi",shape=box];
|
||||
N38[label="winapi-build",shape=box];
|
||||
N39[label="lazy_static",shape=box];
|
||||
N40[label="lazycell",shape=box];
|
||||
N41[label="mio",shape=box];
|
||||
N42[label="miow",shape=box];
|
||||
N43[label="net2",shape=box];
|
||||
N44[label="nix",shape=box];
|
||||
N45[label="slab",shape=box];
|
||||
N46[label="ws2_32-sys",shape=box];
|
||||
N47[label="rustc_version",shape=box];
|
||||
N48[label="semver",shape=box];
|
||||
N49[label="void",shape=box];
|
||||
N50[label="owning_ref",shape=box];
|
||||
N51[label="parking_lot",shape=box];
|
||||
N52[label="time",shape=box];
|
||||
N53[label="tokio-core",shape=box];
|
||||
N54[label="parking_lot_core",shape=box];
|
||||
N55[label="smallvec",shape=box];
|
||||
N56[label="scoped-tls",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];
|
||||
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 -> N12[label="",style=dashed];
|
||||
N1 -> N13[label="",style=dashed];
|
||||
N2 -> N7[label="",style=dashed];
|
||||
N2 -> N15[label="",style=dashed];
|
||||
N1 -> N7[label="",style=dashed];
|
||||
N1 -> N16[label="",style=dashed];
|
||||
N1 -> N22[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 -> N22[label="",style=dashed];
|
||||
N2 -> N23[label="",style=dashed];
|
||||
N2 -> N24[label="",style=dashed];
|
||||
N2 -> N25[label="",style=dashed];
|
||||
N2 -> N26[label="",style=dashed];
|
||||
N2 -> N27[label="",style=dashed];
|
||||
N3 -> N1[label="",style=dashed];
|
||||
N2 -> N30[label="",style=dashed];
|
||||
N2 -> N31[label="",style=dashed];
|
||||
N2 -> N32[label="",style=dashed];
|
||||
N2 -> N33[label="",style=dashed];
|
||||
N3 -> N11[label="",style=dashed];
|
||||
N3 -> N12[label="",style=dashed];
|
||||
N3 -> N19[label="",style=dashed];
|
||||
N3 -> N29[label="",style=dashed];
|
||||
N3 -> N31[label="",style=dashed];
|
||||
N3 -> N39[label="",style=dashed];
|
||||
N4 -> N1[label="",style=dashed];
|
||||
N3 -> N13[label="",style=dashed];
|
||||
N3 -> N20[label="",style=dashed];
|
||||
N3 -> N35[label="",style=dashed];
|
||||
N3 -> N36[label="",style=dashed];
|
||||
N3 -> N46[label="",style=dashed];
|
||||
N4 -> N12[label="",style=dashed];
|
||||
N4 -> N16[label="",style=dashed];
|
||||
N4 -> N18[label="",style=dashed];
|
||||
N4 -> N20[label="",style=dashed];
|
||||
N5 -> N1[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 -> N20[label="",style=dashed];
|
||||
N5 -> N31[label="",style=dashed];
|
||||
N5 -> N13[label="",style=dashed];
|
||||
N5 -> N21[label="",style=dashed];
|
||||
N5 -> N32[label="",style=dashed];
|
||||
N5 -> N34[label="",style=dashed];
|
||||
N5 -> N51[label="",style=dashed];
|
||||
N5 -> N52[label="",style=dashed];
|
||||
N5 -> N53[label="",style=dashed];
|
||||
N6 -> N1[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];
|
||||
N6 -> N18[label="",style=dashed];
|
||||
N6 -> N20[label="",style=dashed];
|
||||
N6 -> N13[label="",style=dashed];
|
||||
N6 -> N19[label="",style=dashed];
|
||||
N6 -> N21[label="",style=dashed];
|
||||
N8 -> N9[label=""];
|
||||
N8 -> N10[label=""];
|
||||
N9 -> N10[label=""];
|
||||
N12 -> N19[label="",style=dashed];
|
||||
N13 -> N19[label="",style=dashed];
|
||||
N13 -> N21[label="",style=dashed];
|
||||
N13 -> N30[label="",style=dashed];
|
||||
N13 -> N31[label="",style=dashed];
|
||||
N13 -> N52[label="",style=dashed];
|
||||
N18 -> N1[label="",style=dashed];
|
||||
N18 -> N12[label="",style=dashed];
|
||||
N18 -> N19[label="",style=dashed];
|
||||
N18 -> N20[label="",style=dashed];
|
||||
N20 -> N12[label="",style=dashed];
|
||||
N20 -> N16[label="",style=dashed];
|
||||
N23 -> N21[label="",style=dashed];
|
||||
N23 -> N36[label="",style=dashed];
|
||||
N23 -> N37[label="",style=dashed];
|
||||
N29 -> N8[label="",style=dashed];
|
||||
N29 -> N19[label="",style=dashed];
|
||||
N29 -> N21[label="",style=dashed];
|
||||
N29 -> N30[label="",style=dashed];
|
||||
N29 -> N31[label="",style=dashed];
|
||||
N31 -> N21[label="",style=dashed];
|
||||
N32 -> N33[label="",style=dashed];
|
||||
N34 -> N28[label="",style=dashed];
|
||||
N34 -> N32[label="",style=dashed];
|
||||
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 -> N21[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];
|
||||
N36 -> N38[label="",style=dashed];
|
||||
N41 -> N21[label="",style=dashed];
|
||||
N41 -> N33[label="",style=dashed];
|
||||
N41 -> N36[label="",style=dashed];
|
||||
N41 -> N37[label="",style=dashed];
|
||||
N41 -> N40[label=""];
|
||||
N41 -> N42[label=""];
|
||||
N41 -> N43[label=""];
|
||||
N41 -> N44[label=""];
|
||||
N41 -> N45[label="",style=dashed];
|
||||
N42 -> N36[label=""];
|
||||
N42 -> N37[label=""];
|
||||
N42 -> N43[label=""];
|
||||
N42 -> N46[label=""];
|
||||
N43 -> N17[label=""];
|
||||
N43 -> N21[label=""];
|
||||
N43 -> N36[label=""];
|
||||
N43 -> N37[label=""];
|
||||
N43 -> N46[label=""];
|
||||
N44 -> N14[label=""];
|
||||
N44 -> N17[label=""];
|
||||
N44 -> N21[label=""];
|
||||
N44 -> N47[label=""];
|
||||
N44 -> N48[label=""];
|
||||
N44 -> N49[label=""];
|
||||
N46 -> N37[label=""];
|
||||
N46 -> N38[label=""];
|
||||
N47 -> N48[label=""];
|
||||
N51 -> N50[label="",style=dashed];
|
||||
N51 -> N54[label="",style=dashed];
|
||||
N52 -> N21[label="",style=dashed];
|
||||
N52 -> N36[label="",style=dashed];
|
||||
N52 -> N37[label="",style=dashed];
|
||||
N53 -> N32[label="",style=dashed];
|
||||
N53 -> N33[label="",style=dashed];
|
||||
N53 -> N41[label="",style=dashed];
|
||||
N53 -> N45[label="",style=dashed];
|
||||
N53 -> N56[label="",style=dashed];
|
||||
N54 -> N21[label="",style=dashed];
|
||||
N54 -> N31[label="",style=dashed];
|
||||
N54 -> N36[label="",style=dashed];
|
||||
N54 -> N37[label="",style=dashed];
|
||||
N54 -> N55[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=""];
|
||||
N49 -> N50[label=""];
|
||||
N49 -> N53[label=""];
|
||||
N50 -> N18[label=""];
|
||||
N50 -> N22[label=""];
|
||||
N50 -> N43[label=""];
|
||||
N50 -> N44[label=""];
|
||||
N50 -> N53[label=""];
|
||||
N51 -> N15[label=""];
|
||||
N51 -> N18[label=""];
|
||||
N51 -> N22[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];
|
||||
}
|
||||
|
|
BIN
tools/graph.png
BIN
tools/graph.png
Binary file not shown.
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 454 KiB |
|
@ -1,6 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
cargo test\
|
||||
-p db\
|
||||
-p ethcore-devtools\
|
||||
-p chain\
|
||||
-p bitcrypto\
|
||||
-p keys\
|
||||
|
|
Loading…
Reference in New Issue