mirror of
https://github.com/romanz/electrs.git
synced 2024-11-19 18:10:51 +01:00
Move query-related code into a separate module
This commit is contained in:
parent
68feda0baa
commit
95da35ac80
@ -6,7 +6,7 @@ extern crate indexrs;
|
|||||||
|
|
||||||
use argparse::{ArgumentParser, StoreFalse};
|
use argparse::{ArgumentParser, StoreFalse};
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use indexrs::{daemon, index, rpc, store, waiter};
|
use indexrs::{daemon, index, query, rpc, store, waiter};
|
||||||
|
|
||||||
fn setup_logging() {
|
fn setup_logging() {
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
@ -51,7 +51,7 @@ fn run_server(config: Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let store = store::Store::open(DB_PATH, store::StoreOptions { auto_compact: true });
|
let store = store::Store::open(DB_PATH, store::StoreOptions { auto_compact: true });
|
||||||
let query = index::Query::new(&store, &daemon);
|
let query = query::Query::new(&store, &daemon);
|
||||||
|
|
||||||
crossbeam::scope(|scope| {
|
crossbeam::scope(|scope| {
|
||||||
scope.spawn(|| rpc::serve("localhost:50001", &query));
|
scope.spawn(|| rpc::serve("localhost:50001", &query));
|
||||||
|
125
src/index.rs
125
src/index.rs
@ -1,12 +1,11 @@
|
|||||||
use bincode;
|
use bincode;
|
||||||
use bitcoin::blockdata::block::{Block, BlockHeader};
|
use bitcoin::blockdata::block::{Block, BlockHeader};
|
||||||
use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut};
|
use bitcoin::blockdata::transaction::{TxIn, TxOut};
|
||||||
use bitcoin::network::serialize::BitcoinHash;
|
use bitcoin::network::serialize::BitcoinHash;
|
||||||
use bitcoin::network::serialize::{deserialize, serialize};
|
use bitcoin::network::serialize::{deserialize, serialize};
|
||||||
use bitcoin::util::hash::Sha256dHash;
|
use bitcoin::util::hash::Sha256dHash;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
use crypto::sha2::Sha256;
|
use crypto::sha2::Sha256;
|
||||||
use itertools::enumerate;
|
|
||||||
use pbr;
|
use pbr;
|
||||||
use std::io::{stderr, Stderr};
|
use std::io::{stderr, Stderr};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -17,12 +16,12 @@ use store::{Row, Store};
|
|||||||
use types::{Bytes, HeaderMap};
|
use types::{Bytes, HeaderMap};
|
||||||
|
|
||||||
const HASH_LEN: usize = 32;
|
const HASH_LEN: usize = 32;
|
||||||
const HASH_PREFIX_LEN: usize = 8;
|
pub const HASH_PREFIX_LEN: usize = 8;
|
||||||
|
|
||||||
type FullHash = [u8; HASH_LEN];
|
type FullHash = [u8; HASH_LEN];
|
||||||
type HashPrefix = [u8; HASH_PREFIX_LEN];
|
pub type HashPrefix = [u8; HASH_PREFIX_LEN];
|
||||||
|
|
||||||
fn hash_prefix(hash: &[u8]) -> HashPrefix {
|
pub fn hash_prefix(hash: &[u8]) -> HashPrefix {
|
||||||
array_ref![hash, 0, HASH_PREFIX_LEN].clone()
|
array_ref![hash, 0, HASH_PREFIX_LEN].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,43 +30,43 @@ fn full_hash(hash: &[u8]) -> FullHash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct TxInKey {
|
pub struct TxInKey {
|
||||||
code: u8,
|
pub code: u8,
|
||||||
prev_hash_prefix: HashPrefix,
|
pub prev_hash_prefix: HashPrefix,
|
||||||
prev_index: u16,
|
pub prev_index: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct TxInRow {
|
pub struct TxInRow {
|
||||||
key: TxInKey,
|
key: TxInKey,
|
||||||
txid_prefix: HashPrefix,
|
pub txid_prefix: HashPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct TxOutKey {
|
pub struct TxOutKey {
|
||||||
code: u8,
|
code: u8,
|
||||||
script_hash_prefix: HashPrefix,
|
script_hash_prefix: HashPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct TxOutRow {
|
pub struct TxOutRow {
|
||||||
key: TxOutKey,
|
key: TxOutKey,
|
||||||
txid_prefix: HashPrefix,
|
pub txid_prefix: HashPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct TxKey {
|
pub struct TxKey {
|
||||||
code: u8,
|
code: u8,
|
||||||
txid: FullHash,
|
pub txid: FullHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct BlockKey {
|
pub struct BlockKey {
|
||||||
code: u8,
|
code: u8,
|
||||||
hash: FullHash,
|
hash: FullHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn digest(data: &[u8]) -> FullHash {
|
pub fn compute_script_hash(data: &[u8]) -> FullHash {
|
||||||
let mut hash = FullHash::default();
|
let mut hash = FullHash::default();
|
||||||
let mut sha2 = Sha256::new();
|
let mut sha2 = Sha256::new();
|
||||||
sha2.input(data);
|
sha2.input(data);
|
||||||
@ -94,7 +93,7 @@ fn txout_row(output: &TxOut, txid: &Sha256dHash) -> Row {
|
|||||||
key: bincode::serialize(&TxOutRow {
|
key: bincode::serialize(&TxOutRow {
|
||||||
key: TxOutKey {
|
key: TxOutKey {
|
||||||
code: b'O',
|
code: b'O',
|
||||||
script_hash_prefix: hash_prefix(&digest(&output.script_pubkey[..])),
|
script_hash_prefix: hash_prefix(&compute_script_hash(&output.script_pubkey[..])),
|
||||||
},
|
},
|
||||||
txid_prefix: hash_prefix(&txid[..]),
|
txid_prefix: hash_prefix(&txid[..]),
|
||||||
}).unwrap(),
|
}).unwrap(),
|
||||||
@ -315,91 +314,3 @@ impl Index {
|
|||||||
self.headers = Some(current_headers)
|
self.headers = Some(current_headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Query<'a> {
|
|
||||||
store: &'a Store,
|
|
||||||
daemon: &'a Daemon,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Query<'a> {
|
|
||||||
pub fn new(store: &'a Store, daemon: &'a Daemon) -> Query<'a> {
|
|
||||||
Query { store, daemon }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_txns(&self, prefixes: Vec<HashPrefix>) -> Vec<Transaction> {
|
|
||||||
let mut txns = Vec::new();
|
|
||||||
for txid_prefix in prefixes {
|
|
||||||
for row in self.store.scan(&[b"T", &txid_prefix[..]].concat()) {
|
|
||||||
let key: TxKey = bincode::deserialize(&row.key).unwrap();
|
|
||||||
let txid: Sha256dHash = deserialize(&key.txid).unwrap();
|
|
||||||
let txn_bytes = self.daemon.get(&format!("tx/{}.bin", txid.be_hex_string()));
|
|
||||||
let txn: Transaction = deserialize(&txn_bytes).unwrap();
|
|
||||||
txns.push(txn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txns
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_spending_txn(&self, txid: &Sha256dHash, output_index: u32) -> Option<Transaction> {
|
|
||||||
let spend_key = bincode::serialize(&TxInKey {
|
|
||||||
code: b'I',
|
|
||||||
prev_hash_prefix: hash_prefix(&txid[..]),
|
|
||||||
prev_index: output_index as u16,
|
|
||||||
}).unwrap();
|
|
||||||
let mut spending: Vec<Transaction> = self.load_txns(
|
|
||||||
self.store
|
|
||||||
.scan(&spend_key)
|
|
||||||
.iter()
|
|
||||||
.map(|row| {
|
|
||||||
bincode::deserialize::<TxInRow>(&row.key)
|
|
||||||
.unwrap()
|
|
||||||
.txid_prefix
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
spending.retain(|item| {
|
|
||||||
item.input
|
|
||||||
.iter()
|
|
||||||
.any(|input| input.prev_hash == *txid && input.prev_index == output_index)
|
|
||||||
});
|
|
||||||
assert!(spending.len() <= 1);
|
|
||||||
if spending.len() == 1 {
|
|
||||||
Some(spending.remove(0))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn balance(&self, script_hash: &[u8]) -> f64 {
|
|
||||||
let mut funding: Vec<Transaction> = self.load_txns(
|
|
||||||
self.store
|
|
||||||
.scan(&[b"O", &script_hash[..HASH_PREFIX_LEN]].concat())
|
|
||||||
.iter()
|
|
||||||
.map(|row| {
|
|
||||||
bincode::deserialize::<TxOutRow>(&row.key)
|
|
||||||
.unwrap()
|
|
||||||
.txid_prefix
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
funding.retain(|item| {
|
|
||||||
item.output
|
|
||||||
.iter()
|
|
||||||
.any(|output| digest(&output.script_pubkey[..]) == script_hash)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut balance = 0u64;
|
|
||||||
let mut spending = Vec::<Transaction>::new();
|
|
||||||
for txn in &funding {
|
|
||||||
let txid = txn.txid();
|
|
||||||
for (index, output) in enumerate(&txn.output) {
|
|
||||||
if let Some(spent) = self.find_spending_txn(&txid, index as u32) {
|
|
||||||
spending.push(spent); // TODO: may contain duplicate TXNs
|
|
||||||
} else {
|
|
||||||
balance += output.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
balance as f64 / 100_000_000f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -24,6 +24,7 @@ extern crate serde_json;
|
|||||||
|
|
||||||
pub mod daemon;
|
pub mod daemon;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod query;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub mod waiter;
|
pub mod waiter;
|
||||||
|
98
src/query.rs
Normal file
98
src/query.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use bincode;
|
||||||
|
use bitcoin::blockdata::transaction::Transaction;
|
||||||
|
use bitcoin::network::serialize::deserialize;
|
||||||
|
use bitcoin::util::hash::Sha256dHash;
|
||||||
|
use itertools::enumerate;
|
||||||
|
|
||||||
|
use daemon::Daemon;
|
||||||
|
use index::{compute_script_hash, hash_prefix, HashPrefix, TxInKey, TxInRow, TxKey, TxOutRow,
|
||||||
|
HASH_PREFIX_LEN};
|
||||||
|
use store::Store;
|
||||||
|
|
||||||
|
pub struct Query<'a> {
|
||||||
|
store: &'a Store,
|
||||||
|
daemon: &'a Daemon,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Query<'a> {
|
||||||
|
pub fn new(store: &'a Store, daemon: &'a Daemon) -> Query<'a> {
|
||||||
|
Query { store, daemon }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_txns(&self, prefixes: Vec<HashPrefix>) -> Vec<Transaction> {
|
||||||
|
let mut txns = Vec::new();
|
||||||
|
for txid_prefix in prefixes {
|
||||||
|
for row in self.store.scan(&[b"T", &txid_prefix[..]].concat()) {
|
||||||
|
let key: TxKey = bincode::deserialize(&row.key).unwrap();
|
||||||
|
let txid: Sha256dHash = deserialize(&key.txid).unwrap();
|
||||||
|
let txn_bytes = self.daemon.get(&format!("tx/{}.bin", txid.be_hex_string()));
|
||||||
|
let txn: Transaction = deserialize(&txn_bytes).unwrap();
|
||||||
|
txns.push(txn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txns
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_spending_txn(&self, txid: &Sha256dHash, output_index: u32) -> Option<Transaction> {
|
||||||
|
let spend_key = bincode::serialize(&TxInKey {
|
||||||
|
code: b'I',
|
||||||
|
prev_hash_prefix: hash_prefix(&txid[..]),
|
||||||
|
prev_index: output_index as u16,
|
||||||
|
}).unwrap();
|
||||||
|
let mut spending: Vec<Transaction> = self.load_txns(
|
||||||
|
self.store
|
||||||
|
.scan(&spend_key)
|
||||||
|
.iter()
|
||||||
|
.map(|row| {
|
||||||
|
bincode::deserialize::<TxInRow>(&row.key)
|
||||||
|
.unwrap()
|
||||||
|
.txid_prefix
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
spending.retain(|item| {
|
||||||
|
item.input
|
||||||
|
.iter()
|
||||||
|
.any(|input| input.prev_hash == *txid && input.prev_index == output_index)
|
||||||
|
});
|
||||||
|
assert!(spending.len() <= 1);
|
||||||
|
if spending.len() == 1 {
|
||||||
|
Some(spending.remove(0))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn balance(&self, script_hash: &[u8]) -> f64 {
|
||||||
|
let mut funding: Vec<Transaction> = self.load_txns(
|
||||||
|
self.store
|
||||||
|
.scan(&[b"O", &script_hash[..HASH_PREFIX_LEN]].concat())
|
||||||
|
.iter()
|
||||||
|
.map(|row| {
|
||||||
|
bincode::deserialize::<TxOutRow>(&row.key)
|
||||||
|
.unwrap()
|
||||||
|
.txid_prefix
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
funding.retain(|item| {
|
||||||
|
item.output
|
||||||
|
.iter()
|
||||||
|
.any(|output| compute_script_hash(&output.script_pubkey[..]) == script_hash)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut balance = 0u64;
|
||||||
|
let mut spending = Vec::<Transaction>::new();
|
||||||
|
for txn in &funding {
|
||||||
|
let txid = txn.txid();
|
||||||
|
for (index, output) in enumerate(&txn.output) {
|
||||||
|
if let Some(spent) = self.find_spending_txn(&txid, index as u32) {
|
||||||
|
spending.push(spent); // TODO: may contain duplicate TXNs
|
||||||
|
} else {
|
||||||
|
balance += output.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balance as f64 / 100_000_000f64
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,12 @@ use serde_json::{from_str, Number, Value};
|
|||||||
use std::io::{BufRead, BufReader, Write};
|
use std::io::{BufRead, BufReader, Write};
|
||||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||||
|
|
||||||
use index;
|
use query::Query;
|
||||||
|
|
||||||
error_chain!{}
|
error_chain!{}
|
||||||
|
|
||||||
struct Handler<'a> {
|
struct Handler<'a> {
|
||||||
query: &'a index::Query<'a>,
|
query: &'a Query<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Handler<'a> {
|
impl<'a> Handler<'a> {
|
||||||
@ -129,7 +129,7 @@ impl<'a> Handler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve(addr: &str, query: &index::Query) {
|
pub fn serve(addr: &str, query: &Query) {
|
||||||
let listener = TcpListener::bind(addr).unwrap();
|
let listener = TcpListener::bind(addr).unwrap();
|
||||||
info!("RPC server running on {}", addr);
|
info!("RPC server running on {}", addr);
|
||||||
loop {
|
loop {
|
||||||
|
Loading…
Reference in New Issue
Block a user