Compare commits

...

6 Commits

Author SHA1 Message Date
Nadav Ivgi
e8fd84371a Detect config changes and require a reindex 2018-11-19 08:03:47 +02:00
Nadav Ivgi
c6c29787a6 Only attach prevout to inputs when "prevout_enabled" is set 2018-11-19 08:01:29 +02:00
Nadav Ivgi
8195475204 Implement bitcoind-based fallbacks for load_txn, get_block_meta and get_block_txids 2018-11-19 08:00:47 +02:00
Nadav Ivgi
9827512c39 Implement optional indexes in indexer 2018-11-19 07:55:16 +02:00
Nadav Ivgi
344083da74 Add new configuration options 2018-11-19 07:34:56 +02:00
Nadav Ivgi
bee94a54b9 Make config available for query and indexer 2018-11-19 07:34:56 +02:00
10 changed files with 223 additions and 102 deletions

View File

@ -1,11 +1,12 @@
use bitcoin::util::hash::Sha256dHash;
use std::sync::{Arc, Mutex};
use {daemon, index, signal::Waiter, store};
use {daemon, index, signal::Waiter, store, config};
use errors::*;
pub struct App {
config: config::Config,
store: store::DBStore,
index: index::Index,
daemon: daemon::Daemon,
@ -14,11 +15,13 @@ pub struct App {
impl App {
pub fn new(
config: config::Config,
store: store::DBStore,
index: index::Index,
daemon: daemon::Daemon,
) -> Result<Arc<App>> {
Ok(Arc::new(App {
config,
store,
index,
daemon: daemon.reconnect()?,
@ -33,6 +36,9 @@ impl App {
pub fn read_store(&self) -> &store::ReadStore {
&self.store
}
pub fn config(&self) -> &config::Config{
&self.config
}
pub fn index(&self) -> &index::Index {
&self.index
}

View File

@ -19,10 +19,10 @@ use electrs::{
metrics::Metrics,
query::Query,
signal::Waiter,
store::{full_compaction, is_fully_compacted, DBStore},
store::{full_compaction, is_fully_compacted, verify_index_compatibility, DBStore},
};
fn run_server(config: &Config) -> Result<()> {
fn run_server(config: Config) -> Result<()> {
let signal = Waiter::new();
let metrics = Metrics::new(config.monitoring_addr);
metrics.start();
@ -37,7 +37,10 @@ fn run_server(config: &Config) -> Result<()> {
)?;
// Perform initial indexing from local blk*.dat block files.
let store = DBStore::open(&config.db_path, /*low_memory=*/ config.jsonrpc_import);
let index = Index::load(&store, &daemon, &metrics, config.index_batch_size)?;
let index = Index::load(&store, &daemon, &metrics, &config)?;
verify_index_compatibility(&store, config.index_settings());
let store = if is_fully_compacted(&store) {
store // initial import and full compaction are over
} else {
@ -46,14 +49,14 @@ fn run_server(config: &Config) -> Result<()> {
full_compaction(store)
} else {
// faster, but uses more memory
let store = bulk::index_blk_files(&daemon, config.bulk_index_threads, &metrics, store)?;
let store = bulk::index_blk_files(&daemon, &config, &metrics, store)?;
let store = full_compaction(store);
index.reload(&store); // make sure the block header index is up-to-date
store
}
}.enable_compaction(); // enable auto compactions before starting incremental index updates.
let app = App::new(store, index, daemon)?;
let app = App::new(config, store, index, daemon)?;
let query = Query::new(app.clone(), &metrics);
let mut server = None; // HTTP REST server
@ -64,7 +67,7 @@ fn run_server(config: &Config) -> Result<()> {
if server.is_none() {
let info = app.daemon().getblockchaininfo()?;
if info.initialblockdownload == false && info.verificationprogress > 0.9999 {
server = Some(rest::run_server(&config, query.clone()));
server = Some(rest::run_server(&app.config(), query.clone()));
} else {
warn!("bitcoind not fully synced waiting");
}
@ -80,7 +83,7 @@ fn run_server(config: &Config) -> Result<()> {
fn main() {
let config = Config::from_args();
if let Err(e) = run_server(&config) {
if let Err(e) = run_server(config) {
error!("server failed: {}", e.display_chain());
process::exit(1);
}

View File

@ -16,6 +16,7 @@ use daemon::Daemon;
use index::{index_block, last_indexed_block, read_indexed_blockhashes};
use metrics::{CounterVec, Histogram, HistogramOpts, HistogramVec, MetricOpts, Metrics};
use store::{DBStore, Row, WriteStore};
use config::Config;
use util::{spawn_thread, HeaderList, SyncChannel};
use errors::*;
@ -28,6 +29,7 @@ struct Parser {
duration: HistogramVec,
block_count: CounterVec,
bytes_read: Histogram,
config: Config,
}
impl Parser {
@ -35,11 +37,13 @@ impl Parser {
daemon: &Daemon,
metrics: &Metrics,
indexed_blockhashes: HashSet<Sha256dHash>,
config: &Config,
) -> Result<Arc<Parser>> {
Ok(Arc::new(Parser {
magic: daemon.magic(),
current_headers: load_headers(daemon)?,
indexed_blockhashes: Mutex::new(indexed_blockhashes),
config: config.clone(), // @fixme
duration: metrics.histogram_vec(
HistogramOpts::new("parse_duration", "blk*.dat parsing duration (in seconds)"),
&["step"],
@ -92,7 +96,7 @@ impl Parser {
.expect("indexed_blockhashes")
.insert(blockhash.clone())
{
rows.extend(index_block(&block, header.height() as u32));
rows.extend(index_block(&block, header.height() as u32, &self.config));
self.block_count.with_label_values(&["indexed"]).inc();
} else {
self.block_count.with_label_values(&["duplicate"]).inc();
@ -209,7 +213,7 @@ fn start_indexer(
pub fn index_blk_files(
daemon: &Daemon,
index_threads: usize,
config: &Config,
metrics: &Metrics,
store: DBStore,
) -> Result<DBStore> {
@ -218,10 +222,10 @@ pub fn index_blk_files(
info!("indexing {} blk*.dat files", blk_files.len());
let indexed_blockhashes = read_indexed_blockhashes(&store);
debug!("found {} indexed blocks", indexed_blockhashes.len());
let parser = Parser::new(daemon, metrics, indexed_blockhashes)?;
let parser = Parser::new(daemon, metrics, indexed_blockhashes, config)?;
let (blobs, reader) = start_reader(blk_files, parser.clone());
let rows_chan = SyncChannel::new(0);
let indexers: Vec<JoinHandle> = (0..index_threads)
let indexers: Vec<JoinHandle> = (0..config.bulk_index_threads)
.map(|_| start_indexer(blobs.clone(), parser.clone(), rows_chan.sender()))
.collect();
Ok(spawn_thread("bulk_writer", move || -> DBStore {

View File

@ -7,8 +7,10 @@ use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use stderrlog;
use bincode;
use daemon::CookieGetter;
use util::Bytes;
use errors::*;
@ -28,6 +30,10 @@ pub struct Config {
pub index_batch_size: usize,
pub bulk_index_threads: usize,
pub tx_cache_size: usize,
pub txstore_enabled: bool,
pub blocktxs_enabled: bool,
pub blockmeta_enabled: bool,
pub prevout_enabled: bool,
}
impl Config {
@ -116,6 +122,26 @@ impl Config {
.help("Number of transactions to keep in for query LRU cache")
.default_value("10000") // should be enough for a small wallet.
)
.arg(
Arg::with_name("enable_txstore")
.long("enable-txstore")
.help("Store full raw transactions in database")
)
.arg(
Arg::with_name("enable_blockmeta")
.long("enable-blockmeta")
.help("Store block metadata in database")
)
.arg(
Arg::with_name("enable_blocktxs")
.long("enable-blocktxs")
.help("Store blockhash to txids map in database")
)
.arg(
Arg::with_name("enable_prevout")
.long("enable-prevout")
.help("Attach previout output details to inputs")
)
.get_matches();
let network_name = m.value_of("network").unwrap_or("mainnet");
@ -211,6 +237,10 @@ impl Config {
index_batch_size: value_t_or_exit!(m, "index_batch_size", usize),
bulk_index_threads,
tx_cache_size: value_t_or_exit!(m, "tx_cache_size", usize),
txstore_enabled: m.is_present("enable_txstore"),
blocktxs_enabled: m.is_present("enable_blocktxs"),
blockmeta_enabled: m.is_present("enable_blockmeta"),
prevout_enabled: m.is_present("enable_prevout"),
};
eprintln!("{:?}", config);
config
@ -227,6 +257,10 @@ impl Config {
})
}
}
pub fn index_settings(&self) -> Bytes {
bincode::serialize(&(self.network_type, self.txstore_enabled, self.blocktxs_enabled, self.blockmeta_enabled)).unwrap()
}
}
struct StaticCookie {

View File

@ -475,6 +475,13 @@ impl Daemon {
Ok(block)
}
pub fn getblock_raw(&self, blockhash: &Sha256dHash, verbose: u32) -> Result<Value> {
self.request(
"getblock",
json!([blockhash.be_hex_string(), verbose]),
)
}
pub fn getblocks(&self, blockhashes: &[Sha256dHash]) -> Result<Vec<Block>> {
let params_list: Vec<Value> = blockhashes
.iter()
@ -491,7 +498,7 @@ impl Daemon {
pub fn gettransaction(
&self,
txhash: &Sha256dHash,
blockhash: Option<Sha256dHash>,
blockhash: Option<&Sha256dHash>,
) -> Result<Transaction> {
let mut args = json!([txhash.be_hex_string(), /*verbose=*/ false]);
if let Some(blockhash) = blockhash {
@ -505,7 +512,7 @@ impl Daemon {
pub fn gettransaction_raw(
&self,
txhash: &Sha256dHash,
blockhash: Option<Sha256dHash>,
blockhash: Option<&Sha256dHash>,
verbose: bool,
) -> Result<Value> {
let mut args = json!([txhash.be_hex_string(), verbose]);

View File

@ -19,6 +19,8 @@ use util::{
HeaderList, HeaderMap, SyncChannel, HASH_PREFIX_LEN,
};
use config::Config;
use errors::*;
#[derive(Serialize, Deserialize)]
@ -229,16 +231,20 @@ pub fn index_transaction(
for output in &txn.output {
rows.push(TxOutRow::new(&txid, &output).to_row());
}
// Persist transaction ID and confirmed height
// Persist transaction ID and confirmed height/hash
rows.push(TxRow::new(&txid, height, blockhash).to_row());
rows.push(RawTxRow::new(&txid, serialize(txn)).to_row()); // @TODO avoid re-serialization
}
pub fn index_block(block: &Block, height: u32) -> Vec<Row> {
pub fn index_block(block: &Block, height: u32, config: &Config) -> Vec<Row> {
let blockhash = block.bitcoin_hash();
let mut rows = vec![];
for txn in &block.txdata {
index_transaction(&txn, height, &blockhash, &mut rows);
// Persist raw transaction to txstore
if config.txstore_enabled {
rows.push(RawTxRow::new(&txn.txid(), serialize(txn)).to_row()); // @TODO avoid re-serialization
}
}
let blockhash = block.bitcoin_hash();
// Persist block hash and header
@ -251,27 +257,28 @@ pub fn index_block(block: &Block, height: u32) -> Vec<Row> {
});
// Persist block metadata (size, number of txs and sum of txs weight)
let blockmeta = BlockMeta::from(block);
rows.push(Row {
key: bincode::serialize(&BlockKey {
code: b'M',
hash: full_hash(&blockhash[..]),
}).unwrap(),
value: bincode::serialize(&blockmeta).unwrap(),
});
// @XXX block metadata could be saved alongside the header and added
// into the HeaderList structure, which would be more efficient but
// require more invasive changes in electrs internals.
if config.blockmeta_enabled {
let blockmeta = BlockMeta::from(block);
rows.push(Row {
key: bincode::serialize(&BlockKey {
code: b'M',
hash: full_hash(&blockhash[..]),
}).unwrap(),
value: bincode::serialize(&blockmeta).unwrap(),
});
}
// Persist list of txids in block
let txids: Vec<Sha256dHash> = block.txdata.iter().map(|tx| tx.txid()).collect();
rows.push(Row {
key: bincode::serialize(&BlockKey {
code: b'X',
hash: full_hash(&blockhash[..]),
}).unwrap(),
value: bincode::serialize(&txids).unwrap(),
});
if config.blocktxs_enabled {
let txids: Vec<Sha256dHash> = block.txdata.iter().map(|tx| tx.txid()).collect();
rows.push(Row {
key: bincode::serialize(&BlockKey {
code: b'X',
hash: full_hash(&blockhash[..]),
}).unwrap(),
value: bincode::serialize(&txids).unwrap(),
});
}
rows
}
@ -380,7 +387,7 @@ pub struct Index {
headers: RwLock<HeaderList>,
daemon: Daemon,
stats: Stats,
batch_size: usize,
config: Config,
}
impl Index {
@ -388,7 +395,7 @@ impl Index {
store: &ReadStore,
daemon: &Daemon,
metrics: &Metrics,
batch_size: usize,
config: &Config,
) -> Result<Index> {
let stats = Stats::new(metrics);
let headers = read_indexed_headers(store);
@ -397,7 +404,7 @@ impl Index {
headers: RwLock::new(headers),
daemon: daemon.reconnect()?,
stats,
batch_size,
config: config.clone(), // @fixme
})
}
@ -452,7 +459,7 @@ impl Index {
let chan = SyncChannel::new(1);
let sender = chan.sender();
let blockhashes: Vec<Sha256dHash> = new_headers.iter().map(|h| *h.hash()).collect();
let batch_size = self.batch_size;
let batch_size = self.config.index_batch_size;
let fetcher = spawn_thread("fetcher", move || {
for chunk in blockhashes.chunks(batch_size) {
sender
@ -483,7 +490,7 @@ impl Index {
.expect(&format!("missing header for block {}", blockhash));
let timer = self.stats.start_timer("index");
let mut block_rows = index_block(block, height as u32);
let mut block_rows = index_block(block, height as u32, &self.config);
block_rows.push(last_indexed_block(&blockhash));
rows.extend(block_rows);
timer.observe_duration();

View File

@ -224,7 +224,7 @@ impl Query {
for txid_prefix in prefixes {
for tx_row in txrows_by_prefix(store, &txid_prefix) {
let txid: Sha256dHash = deserialize(&tx_row.key.txid).unwrap();
let txn = self.tx_get(&txid).chain_err(|| "cannot locate tx")?;
let txn = self.load_txn(&txid, Some(&tx_row.blockhash)).chain_err(|| "cannot locate tx")?;
txns.push(TxnHeight {
txn,
height: tx_row.height,
@ -395,30 +395,44 @@ impl Query {
Ok(blockhash)
}
// Internal API for transaction retrieval (uses bitcoind)
fn _load_txn(&self, tx_hash: &Sha256dHash, block_height: u32) -> Result<Transaction> {
let blockhash = self.lookup_confirmed_blockhash(tx_hash, Some(block_height))?;
self.app.daemon().gettransaction(tx_hash, blockhash)
// Load transaction by txid
pub fn load_txn(&self, txid: &Sha256dHash, blockhash: Option<&Sha256dHash>) -> Result<Transaction> {
if self.app.config().txstore_enabled {
// fetch from our txstore or mempool tracker
rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| deserialize(&row.rawtx).expect("cannot parse tx from txstore"))
.or_else(|| self.tracker.read().unwrap().get_txn(&txid))
.chain_err(|| format!("cannot find tx {}", txid))
} else {
// fetch from bitcoind
let blockhash_from_index: Option<Sha256dHash> = match blockhash {
Some(_) => None,
None => self.lookup_confirmed_blockhash(txid, None)?,
};
let blockhash: Option<&Sha256dHash> = blockhash.or(blockhash_from_index.as_ref());
self.app.daemon().gettransaction(txid, blockhash)
}
}
// Get transaction from txstore or the in-memory mempool Tracker
pub fn tx_get(&self, txid: &Sha256dHash) -> Option<Transaction> {
rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| deserialize(&row.rawtx).expect("cannot parse tx from txstore"))
.or_else(|| self.tracker.read().unwrap().get_txn(&txid))
}
// Get raw transaction from txstore or the in-memory mempool Tracker
pub fn tx_get_raw(&self, txid: &Sha256dHash) -> Option<Bytes> {
rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| row.rawtx)
.or_else(|| {
self.tracker
.read()
.unwrap()
.get_txn(&txid)
.map(|tx| serialize(&tx))
})
// Load raw transaction by txid
pub fn load_raw_txn(&self, txid: &Sha256dHash, blockhash: Option<&Sha256dHash>) -> Result<Bytes> {
if self.app.config().txstore_enabled {
// fetch from our txstore or mempool tracker
Ok(rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| row.rawtx)
.or_else(|| self.tracker.read().unwrap().get_txn(&txid).map(|tx| serialize(&tx)))
.chain_err(|| format!("cannot find tx {}", txid))?
)
} else {
// fetch from bitcoind
let blockhash_from_index: Option<Sha256dHash> = match blockhash {
Some(_) => None,
None => self.lookup_confirmed_blockhash(txid, None)?,
};
let blockhash: Option<&Sha256dHash> = blockhash.or(blockhash_from_index.as_ref());
let tx_val = self.app.daemon().gettransaction_raw(txid, blockhash, false)?;
Ok(::hex::decode(tx_val.as_str().chain_err(|| "non-string tx hex")?).chain_err(|| "invalid hex")?)
}
}
// Public API for transaction retrieval (for Electrum RPC)
@ -427,7 +441,7 @@ impl Query {
let blockhash = self.lookup_confirmed_blockhash(tx_hash, /*block_height*/ None)?;
self.app
.daemon()
.gettransaction_raw(tx_hash, blockhash, verbose)
.gettransaction_raw(tx_hash, blockhash.as_ref(), verbose)
}
pub fn get_block(&self, blockhash: &Sha256dHash) -> Result<Block> {
@ -435,14 +449,35 @@ impl Query {
}
pub fn get_block_header_with_meta(&self, blockhash: &Sha256dHash) -> Result<BlockHeaderMeta> {
let header_entry = self.get_header_by_hash(blockhash)?;
let meta =
get_block_meta(self.app.read_store(), blockhash).ok_or("cannot load block meta")?;
Ok(BlockHeaderMeta { header_entry, meta })
Ok(BlockHeaderMeta {
header_entry: self.get_header_by_hash(blockhash)?,
meta: self.get_block_meta(blockhash)?,
})
}
pub fn get_block_txids(&self, blockhash: &Sha256dHash) -> Result<Vec<Sha256dHash>> {
Ok(get_block_txids(self.app.read_store(), blockhash).ok_or("cannot load block txids")?)
if self.app.config().blocktxs_enabled {
// fetch from our blockhash=>txids index
get_block_txids(self.app.read_store(), blockhash).chain_err(|| "cannot load block txids")
} else {
// fetch from bitcoind
let block = self.app.daemon().getblock_raw(blockhash, 1).chain_err(|| "cannot load block")?;
let txids = block
.get("tx").chain_err(|| "block missing txids")?
.as_array().chain_err(|| "invalid block txids")?;
Ok(txids.iter().map(|txid| Sha256dHash::from_hex(txid.as_str().chain_err(|| "txid not string")?).chain_err(|| "invalid hex")).collect::<Result<Vec<Sha256dHash>>>()?)
}
}
pub fn get_block_meta(&self, blockhash: &Sha256dHash) -> Result<BlockMeta> {
if self.app.config().blockmeta_enabled {
// fetch from our blockhash=>txids index
get_block_meta(self.app.read_store(), blockhash).chain_err(|| "cannot load block meta")
} else {
// fetch from bitcoind
BlockMeta::parse_getblock(self.app.daemon().getblock_raw(blockhash, 1)?)
}
}
pub fn get_headers(&self, heights: &[usize]) -> Vec<HeaderEntry> {

View File

@ -315,12 +315,14 @@ fn attach_txs_data(txs: &mut Vec<TransactionValue>, config: &Config, query: &Arc
for mut tx in txs.iter_mut() {
// collect lookups
for mut vin in tx.vin.iter_mut() {
if !vin.is_coinbase {
lookups
.entry(vin.txid)
.or_insert(vec![])
.push((vin.vout, vin));
if config.prevout_enabled {
for mut vin in tx.vin.iter_mut() {
if !vin.is_coinbase {
lookups
.entry(vin.txid)
.or_insert(vec![])
.push((vin.vout, vin));
}
}
}
// attach encoded address (should ideally happen in TxOutValue::from(), but it cannot
@ -332,30 +334,34 @@ fn attach_txs_data(txs: &mut Vec<TransactionValue>, config: &Config, query: &Arc
}
// fetch prevtxs and attach prevouts to nextins
for (prev_txid, prev_vouts) in lookups {
let prevtx = query.tx_get(&prev_txid).unwrap();
for (prev_out_idx, ref mut nextin) in prev_vouts {
let mut prevout = TxOutValue::from(prevtx.output[prev_out_idx as usize].clone());
prevout.scriptpubkey_address =
script_to_address(&prevout.scriptpubkey, &config.network_type);
nextin.prevout = Some(prevout);
if config.prevout_enabled {
for (prev_txid, prev_vouts) in lookups {
let prevtx = query.load_txn(&prev_txid, None).unwrap();
for (prev_out_idx, ref mut nextin) in prev_vouts {
let mut prevout = TxOutValue::from(prevtx.output[prev_out_idx as usize].clone());
prevout.scriptpubkey_address =
script_to_address(&prevout.scriptpubkey, &config.network_type);
nextin.prevout = Some(prevout);
}
}
}
}
// attach tx fee
for mut tx in txs.iter_mut() {
if tx.vin.iter().any(|vin| vin.prevout.is_none()) {
continue;
}
if config.prevout_enabled {
for mut tx in txs.iter_mut() {
if tx.vin.iter().any(|vin| vin.prevout.is_none()) {
continue;
}
let total_in: u64 = tx
.vin
.iter()
.map(|vin| vin.clone().prevout.unwrap().value)
.sum();
let total_out: u64 = tx.vout.iter().map(|vout| vout.value).sum();
tx.fee = Some(total_in - total_out);
let total_in: u64 = tx
.vin
.iter()
.map(|vin| vin.clone().prevout.unwrap().value)
.sum();
let total_out: u64 = tx.vout.iter().map(|vout| vout.value).sum();
tx.fee = Some(total_in - total_out);
}
}
}
@ -477,9 +483,8 @@ fn handle_request(
.take(TX_LIMIT)
.map(|txid| {
query
.tx_get(&txid)
.load_txn(&txid, Some(&hash))
.map(TransactionValue::from)
.ok_or("missing tx".to_string())
}).collect::<Result<Vec<TransactionValue>, _>>()?;
attach_txs_data(&mut txs, config, query);
json_response(txs, TTL_LONG)
@ -553,8 +558,8 @@ fn handle_request(
(&Method::GET, Some(&"tx"), Some(hash), None, None) => {
let hash = Sha256dHash::from_hex(hash)?;
let transaction = query
.tx_get(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
.load_txn(&hash, None)
.map_err(|_| HttpError::not_found("Transaction not found".to_string()))?;
let status = query.get_tx_status(&hash)?;
let ttl = ttl_by_depth(status.block_height, query);
@ -566,8 +571,8 @@ fn handle_request(
(&Method::GET, Some(&"tx"), Some(hash), Some(&"hex"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let rawtx = query
.tx_get_raw(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
.load_raw_txn(&hash, None)
.map_err(|_| HttpError::not_found("Transaction not found".to_string()))?;
let ttl = ttl_by_depth(query.get_tx_status(&hash)?.block_height, query);
http_message(StatusCode::OK, hex::encode(rawtx), ttl)
}
@ -609,8 +614,8 @@ fn handle_request(
(&Method::GET, Some(&"tx"), Some(hash), Some(&"outspends"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let tx = query
.tx_get(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
.load_txn(&hash, None)
.map_err(|_| HttpError::not_found("Transaction not found".to_string()))?;
let spends: Vec<SpendingValue> = query
.find_spending_for_funding_tx(tx)?
.into_iter()

View File

@ -198,3 +198,12 @@ pub fn is_fully_compacted(store: &ReadStore) -> bool {
let marker = store.get(&full_compaction_marker().key);
marker.is_some()
}
pub fn verify_index_compatibility(store: &DBStore, settings: Bytes) {
match store.get(b"C") {
None => store.write(vec![ Row { key: b"C".to_vec(), value: settings } ]),
Some(x) => if x != settings {
panic!("Incompatible database found. Updating the config options --enable-{txstore,blockmeta,blocktxs} requires a reindex.");
},
}
}

View File

@ -1,6 +1,7 @@
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::consensus::encode::serialize;
use bitcoin::util::hash::{BitcoinHash, Sha256dHash};
use errors::*;
use std::collections::HashMap;
use std::fmt;
use std::iter::FromIterator;
@ -80,6 +81,16 @@ impl<'a> From<&'a Block> for BlockMeta {
}
}
impl BlockMeta {
pub fn parse_getblock(val: ::serde_json::Value) -> Result<BlockMeta> {
Ok(BlockMeta {
tx_count: val.get("nTx").chain_err(|| "missing nTx")?.as_f64().chain_err(|| "nTx not a number")? as u32,
size: val.get("size").chain_err(|| "missing size")?.as_f64().chain_err(|| "size not a number")? as u32,
weight: val.get("weight").chain_err(|| "missing weight")?.as_f64().chain_err(|| "weight not a number")? as u32,
})
}
}
#[derive(Eq, PartialEq, Clone)]
pub struct HeaderEntry {
height: usize,