Compare commits
6 Commits
mempool
...
bitcoin_e-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8fd84371a | ||
|
|
c6c29787a6 | ||
|
|
8195475204 | ||
|
|
9827512c39 | ||
|
|
344083da74 | ||
|
|
bee94a54b9 |
@ -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
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
12
src/bulk.rs
12
src/bulk.rs
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]);
|
||||
|
||||
61
src/index.rs
61
src/index.rs
@ -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();
|
||||
|
||||
93
src/query.rs
93
src/query.rs
@ -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> {
|
||||
|
||||
69
src/rest.rs
69
src/rest.rs
@ -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()
|
||||
|
||||
@ -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.");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
11
src/util.rs
11
src/util.rs
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user