Skip to content

Commit 981fc75

Browse files
author
gcarq
committed
Merge branch 'release/0.6.0'
2 parents 8b90c21 + 6d3355c commit 981fc75

File tree

12 files changed

+156
-247
lines changed

12 files changed

+156
-247
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusty-blockparser"
3-
version = "0.5.4"
3+
version = "0.6.0"
44
authors = ["gcarq <michael.egger@tsn.at>"]
55
include = ["src/*", "sql/*", "LICENSE", "README.md", "Cargo.toml"]
66
description = "Multithreaded Blockchain Parser for most common Cryptocurrencies based on Bitcoin"
@@ -13,12 +13,12 @@ license = "GPL-3.0"
1313
[dependencies]
1414
time = "~0.1"
1515
log = "~0.3"
16-
clap = "~2.9"
16+
clap = "~2.16"
1717
rust-crypto = "~0.2"
1818
rustc-serialize = "~0.3"
1919
byteorder = "~0.5"
2020
rust-base58 = "~0.0"
21-
21+
seek_bufread = "~1.2"
2222

2323
# The development profile, used for `cargo build`
2424
[profile.dev]

README.md

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ It assumes a local copy of the blockchain, typically downloaded by Bitcoin core.
1515
The program flow is split up in two parts.
1616
Lets call it ParseModes:
1717

18-
* **HeaderOnly**
18+
* **Indexing**
1919

2020
If the parser is started the first time, it iterates over all blk.dat files and seeks from header to header. It doesn't evaluates the whole block it just calculates the block hashes to determine the main chain. So we only need to keep ~50 Mb in RAM instead of the whole Blockchain. This process is very fast and takes only **7-8 minutes with 2-3 threads and a average HDD (bottleneck here is I/O)***.
2121
The main chain is saved as a JSON file, lets call it ChainStorage. (The path can be specified with `--chain-storage`)
@@ -117,7 +117,7 @@ Transaction Types:
117117

118118
* **Resume scans**
119119

120-
If you sync the blockchain at some point later, you don't need to make a FullData rescan. Just use `--resume` to force a HeaderOnly scan followed by a FullData scan which parses only new blocks. If you want a complete FullData rescan delete the ChainStorage json file.
120+
If you sync the blockchain at some point later, you don't need to make a FullData rescan. Just use `--resume` to force a Reindexing followed by a FullData scan which parses only new blocks. If you want a complete FullData rescan delete the ChainStorage json file.
121121

122122
## Installing
123123

@@ -179,51 +179,42 @@ Now export this wrappper with: `export RUSTC="./rustc-wrapper.sh"` and execute `
179179

180180
## Usage
181181
```
182-
Usage:
183-
target/debug/rusty-blockparser [OPTIONS] CALLBACK ARGUMENTS [...]
184-
185-
Multithreaded Blockchain Parser written in Rust
186-
187-
positional arguments:
188-
callback Set a callback to execute. See `--list-callbacks`
189-
arguments All following arguments are consumed by this callback.
190-
191-
optional arguments:
192-
-h,--help show this help message and exit
193-
--list-coins Lists all implemented coins
194-
--list-callbacks Lists all available callbacks
195-
-c,--coin COINNAME Specify blockchain coin (default: bitcoin)
196-
-d,--blockchain-dir PATH
197-
Set blockchain directory which contains blk.dat files
198-
(default: ~/.bitcoin/blocks)
199-
--verify-merkle-root BOOL
200-
Verify merkle root (default: false)
201-
-t,--threads COUNT Thread count (default: 2)
202-
-r,--resume Resume from latest known block
203-
--new Force complete rescan
204-
-s,--chain-storage PATH
205-
Specify path to chain storage. This is just a internal
206-
state file (default: chain.json)
207-
--backlog COUNT Set maximum worker backlog (default: 100)
208-
-v,--verbose Increases verbosity level. Error=0, Info=1, Debug=2,
209-
Trace=3 (default: 1)
210-
--version Show version
211-
182+
USAGE:
183+
rusty-blockparser [FLAGS] [OPTIONS] [SUBCOMMAND]
184+
185+
FLAGS:
186+
-h, --help Prints help information
187+
-n, --reindex Force complete reindexing
188+
-r, --resume Resume from latest known block
189+
-V, --version Prints version information
190+
-v Increases verbosity level. Info=0, Debug=1, Trace=2 (default: 0)
191+
--verify-merkle-root Verifies the merkle root of each block
192+
193+
OPTIONS:
194+
--backlog <COUNT> Sets maximum worker backlog (default: 100)
195+
-d, --blockchain-dir <blockchain-dir> Sets blockchain directory which contains blk.dat files (default: ~/.bitcoin/blocks)
196+
--chain-storage <FILE> Specify path to chain storage. This is just a internal state file (default: chain.json)
197+
-c, --coin <NAME> Specify blockchain coin (default: bitcoin) [values: bitcoin, testnet3, namecoin, litecoin, dogecoin, myriadcoin,
198+
unobtanium]
199+
-t, --threads <COUNT> Thread count (default: 2)
200+
201+
SUBCOMMANDS:
202+
csvdump Dumps the whole blockchain into CSV files
203+
help Prints this message or the help of the given subcommand(s)
204+
simplestats Shows various Blockchain stats
212205
```
213206
### Example
214207

215208
To make a `csvdump` of the Bitcoin blockchain your command would look like this:
216209
```
217210
# ./blockparser -t 3 csvdump /path/to/dump/
218-
[00:42:19] INFO - main: Starting blockparser-0.3.0 ...
219-
[00:42:19] INFO - init: No header file found. Generating a new one ...
211+
[00:42:19] INFO - main: Starting rusty-blockparser v0.6.0 ...
220212
[00:42:19] INFO - blkfile: Reading files from folder: ~/.bitcoin/blocks
221-
[00:42:19] INFO - parser: Parsing with mode HeaderOnly (first run).
213+
[00:42:19] INFO - parser: Building blockchain index ...
222214
...
223215
[00:50:46] INFO - dispatch: All threads finished.
224216
[00:50:46] INFO - dispatch: Done. Processed 393496 blocks in 8.45 minutes. (avg: 776 blocks/sec)
225217
[00:50:47] INFO - chain: Inserted 393489 new blocks ...
226-
[00:50:48] INFO - main: Iteration 1 finished.
227218
[00:50:49] INFO - blkfile: Reading files from folder: ~/.bitcoin/blocks
228219
[00:50:49] INFO - parser: Parsing 393489 blocks with mode FullData.
229220
[00:50:49] INFO - callback: Using `csvdump` with dump folder: csv-dump/ ...
@@ -234,8 +225,6 @@ Dumped all blocks: 393489
234225
-> transactions: 103777752
235226
-> inputs: 274278239
236227
-> outputs: 308285408
237-
[02:04:42] INFO - chain: Inserted 0 new blocks ...
238-
[02:04:42] INFO - main: Iteration 2 finished.
239228
```
240229

241230

src/blockchain/parser/chain.rs

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,15 @@ impl ChainStorage {
6464
let latest_known_idx = transform!(headers.iter().position(|h| h.hash == latest_hash));
6565

6666
let mut new_hashes = hashes.split_off(latest_known_idx + 1);
67-
6867
if new_hashes.len() > 0 {
69-
debug!(target: "chain.extend", "\n -> latest known: {}\n -> first new: {}",
68+
debug!(target: "chain", "\n -> latest known block: {}\n -> first new block: {}",
7069
utils::arr_to_hex_swapped(transform!(self.hashes.last())),
7170
utils::arr_to_hex_swapped(transform!(new_hashes.first())));
71+
self.hashes.append(&mut new_hashes);
7272
}
73-
self.hashes.append(&mut new_hashes);
7473
}
74+
debug!(target: "chain", "Inserted {} new blocks ...", self.hashes.len() - self.hashes_len);
7575
}
76-
77-
debug!(target: "chain", "Inserted {} new blocks ...", self.hashes.len() - self.hashes_len);
7876
self.hashes_len = self.hashes.len();
7977
self.latest_blk_idx = latest_blk_idx;
8078
Ok(())
@@ -88,7 +86,7 @@ impl ChainStorage {
8886
try!(file.read_to_string(&mut encoded));
8987

9088
let storage = try!(json::decode::<ChainStorage>(&encoded));
91-
debug!(target: "chain.load", "Imported {} hashes from {}. Current block height: {} ... (latest blk.dat index: {})",
89+
debug!(target: "chain", "Imported {} hashes from {}. Current block height: {} ... (latest blk.dat index: {})",
9290
storage.hashes.len(), path.display(), storage.get_cur_height(), storage.latest_blk_idx);
9391
Ok(storage)
9492
}
@@ -98,7 +96,7 @@ impl ChainStorage {
9896
let encoded = try!(json::encode(&self));
9997
let mut file = try!(File::create(&path));
10098
try!(file.write_all(encoded.as_bytes()));
101-
debug!(target: "chain.serialize", "Serialized {} hashes to {}. Current block height: {} ... (latest blk.dat index: {})",
99+
debug!(target: "chain", "Serialized {} hashes to {}. Latest processed block height: {} ... (latest blk.dat index: {})",
102100
self.hashes.len(), path.display(), self.get_cur_height(), self.latest_blk_idx);
103101
Ok(encoded.len())
104102
}
@@ -116,7 +114,7 @@ impl ChainStorage {
116114
if self.index < self.hashes_len {
117115
self.index += 1;
118116
} else {
119-
panic!("consume_next() index > len");
117+
panic!("FATAL: consume_next() index > len! Please report this issue.");
120118
}
121119
}
122120

@@ -198,7 +196,6 @@ impl<'a> ChainBuilder<'a> {
198196
}
199197

200198

201-
202199
impl<'a> IntoIterator for &'a ChainBuilder<'a> {
203200
type Item = Hashed<BlockHeader>;
204201
type IntoIter = RevBlockIterator<'a>;
@@ -271,8 +268,7 @@ mod tests {
271268
use blockchain::parser::types::{CoinType, Bitcoin};
272269

273270
#[test]
274-
fn test_chain_storage() {
275-
271+
fn chain_storage() {
276272
let mut chain_storage = ChainStorage::default();
277273
let new_header = BlockHeader::new(
278274
0x00000001,
@@ -315,7 +311,60 @@ mod tests {
315311

316312
#[test]
317313
#[should_panic]
318-
fn test_load_bogus_chain_storage() {
314+
fn chain_storage_insert_bogus_header() {
315+
let mut chain_storage = ChainStorage::default();
316+
let new_header = BlockHeader::new(
317+
0x00000001,
318+
[0u8; 32],
319+
[0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
320+
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
321+
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
322+
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a],
323+
1231006505,
324+
0x1d00ffff,
325+
2083236893);
326+
327+
assert_eq!(0, chain_storage.latest_blk_idx);
328+
assert_eq!(0, chain_storage.get_cur_height());
329+
330+
// Extend storage and match genesis block
331+
let coin_type = CoinType::from(Bitcoin);
332+
chain_storage.extend(vec![Hashed::double_sha256(new_header)], &coin_type, 1).unwrap();
333+
assert_eq!(coin_type.genesis_hash, chain_storage.get_next().unwrap());
334+
assert_eq!(1, chain_storage.latest_blk_idx);
335+
336+
// try to insert same header again
337+
let same_header = BlockHeader::new(
338+
0x00000001,
339+
[0u8; 32],
340+
[0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
341+
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
342+
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
343+
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a],
344+
1231006505,
345+
0x1d00ffff,
346+
2083236893);
347+
chain_storage.extend(vec![Hashed::double_sha256(same_header)], &coin_type, 1).unwrap();
348+
assert_eq!(coin_type.genesis_hash, chain_storage.get_next().unwrap());
349+
assert_eq!(1, chain_storage.latest_blk_idx);
350+
351+
// try to insert bogus header
352+
let bogus_header = BlockHeader::new(
353+
0x00000001,
354+
[1u8; 32],
355+
[0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
356+
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
357+
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
358+
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a],
359+
1231006505,
360+
0x1d00ffff,
361+
2083236893);
362+
chain_storage.extend(vec![Hashed::double_sha256(bogus_header)], &coin_type, 1).unwrap();
363+
}
364+
365+
#[test]
366+
#[should_panic]
367+
fn load_bogus_chain_storage() {
319368
// Must fail
320369
let encoded = String::from("AABAAAFKAAANANFANAAMMDDMDAMDADNNDANANDNAVCACANAFMAFAMMAMDAMDM");
321370
match json::decode::<ChainStorage>(&encoded) {
@@ -326,7 +375,7 @@ mod tests {
326375

327376
#[test]
328377
#[should_panic]
329-
fn test_serialize_bogus_chain_storage() {
378+
fn serialize_bogus_chain_storage() {
330379
let encoded = String::from("AABAAAFKAAANANFANAAMMDDMDAMDADNNDANANDNAVCACANAFMAFAMMAMDAMDM");
331380
match json::decode::<ChainStorage>(&encoded) {
332381
Ok(_) => return,

src/blockchain/parser/mod.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@ pub mod worker;
1717
pub mod chain;
1818
pub mod types;
1919

20-
/// Specifies ParseMode. The first time the blockchain is scanned with HeaderOnly,
20+
/// Specifies ParseMode. The first time the blockchain needs to be indexed,
2121
/// because we just need the block hashes to determine the longest chain.
2222
#[derive(Clone, Debug, PartialEq)]
2323
pub enum ParseMode {
2424
FullData,
25-
HeaderOnly
25+
Indexing
2626
}
2727

2828
/// Wrapper to pass different data between threads. Specified by ParseMode
2929
pub enum ParseResult {
3030
FullData(Block),
31-
HeaderOnly(BlockHeader),
31+
Indexing(BlockHeader),
3232
Complete(String), // contains the name of the finished thread
33-
Error(OpError) // Indicates critical error
33+
Error(OpError) // Indicates critical error
3434
}
3535

3636
/// Small struct to hold statistics together
@@ -48,7 +48,7 @@ pub struct BlockchainParser<'a> {
4848
unsorted_blocks: HashMap<[u8; 32], Block>, /* holds all blocks in parse mode FullData */
4949
remaining_files: Arc<Mutex<VecDeque<BlkFile>>>, /* Remaining files (shared between all threads) */
5050
h_workers: Vec<JoinHandle<()>>, /* Worker job handles */
51-
mode: ParseMode, /* ParseMode (FullData or HeaderOnly) */
51+
mode: ParseMode, /* ParseMode (FullData or Indexing) */
5252
options: &'a mut ParserOptions, /* struct to hold cli arguments */
5353
chain_storage: chain::ChainStorage, /* Hash storage with the longest chain */
5454
stats: WorkerStats, /* struct for thread management & statistics */
@@ -57,16 +57,16 @@ pub struct BlockchainParser<'a> {
5757

5858
impl<'a> BlockchainParser<'a> {
5959

60-
/// Instantiats a new Parser but does not start the workers.
60+
/// Instantiates a new Parser but does not start the workers.
6161
pub fn new(options: &'a mut ParserOptions,
6262
parse_mode: ParseMode,
6363
blk_files: VecDeque<BlkFile>,
6464
chain_storage: chain::ChainStorage) -> Self {
6565

6666
info!(target: "parser", "Parsing {} blockchain ...", options.coin_type.name);
6767
match parse_mode {
68-
ParseMode::HeaderOnly => {
69-
info!(target: "parser", "Parsing with mode HeaderOnly (first run).");
68+
ParseMode::Indexing => {
69+
info!(target: "parser", "Building blockchain index ...");
7070
}
7171
ParseMode::FullData => {
7272
info!(target: "parser", "Parsing {} blocks with mode FullData.", chain_storage.remaining());
@@ -95,7 +95,7 @@ impl<'a> BlockchainParser<'a> {
9595

9696
// save latest blk file index for resume mode.
9797
self.stats.latest_blk_idx = match self.mode {
98-
ParseMode::HeaderOnly => self.chain_storage.latest_blk_idx,
98+
ParseMode::Indexing => self.chain_storage.latest_blk_idx,
9999
ParseMode::FullData => transform!(try!(self.remaining_files.lock()).back()).index
100100
};
101101

@@ -151,8 +151,8 @@ impl<'a> BlockchainParser<'a> {
151151
if now - t_last_log > t_measure_frame {
152152
let blocks_sec = self.stats.n_valid_blocks.checked_div((now - self.t_started) as u64).unwrap_or(1);
153153
match self.mode {
154-
ParseMode::HeaderOnly => {
155-
info!(target:"dispatch", "Status: {:6} Headers scanned. (avg: {:5.2} blocks/sec)",
154+
ParseMode::Indexing => {
155+
info!(target:"dispatch", "Status: {:6} Blocks added to index. (avg: {:5.2} blocks/sec)",
156156
self.stats.n_valid_blocks, blocks_sec);
157157
}
158158
ParseMode::FullData => {
@@ -202,7 +202,7 @@ impl<'a> BlockchainParser<'a> {
202202
}
203203
}
204204
// Collect headers to built a valid blockchain
205-
ParseResult::HeaderOnly(header) => {
205+
ParseResult::Indexing(header) => {
206206
let header = Hashed::double_sha256(header);
207207
self.unsorted_headers.insert(header.hash, header.value);
208208
self.stats.n_valid_blocks += 1;
@@ -249,10 +249,10 @@ impl<'a> BlockchainParser<'a> {
249249

250250
/// Searches for the longest chain and writes the hashes t
251251
fn save_chain_state(&mut self) -> OpResult<usize> {
252-
debug!(target: "dispatch", "Saving block headers as {}", self.options.chain_storage_path.display());
252+
info!(target: "dispatch", "Saving block headers as {} ...", self.options.chain_storage_path.display());
253253
// Update chain storage
254254
let headers = match self.mode {
255-
ParseMode::HeaderOnly => try!(chain::ChainBuilder::extract_blockchain(&self.unsorted_headers)),
255+
ParseMode::Indexing => try!(chain::ChainBuilder::extract_blockchain(&self.unsorted_headers)),
256256
ParseMode::FullData => Vec::new()
257257
};
258258
try!(self.chain_storage.extend(headers, &self.options.coin_type, self.stats.latest_blk_idx));

src/blockchain/parser/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl Coin for Dash {
9494
}*/
9595

9696
#[derive(Clone)]
97-
// Holds the selected coin type informations
97+
// Holds the selected coin type information
9898
pub struct CoinType {
9999
pub name: String,
100100
pub magic: u32,

0 commit comments

Comments
 (0)