mirror of
https://github.com/romanz/electrs.git
synced 2025-02-24 15:02:21 +01:00
Create simple mempool tracker
This commit is contained in:
parent
c8a5cb9c56
commit
eab64181bb
7 changed files with 118 additions and 19 deletions
|
@ -34,6 +34,17 @@ pub struct Daemon {
|
||||||
cookie_b64: String,
|
cookie_b64: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MempoolEntry {
|
||||||
|
fee: u64, // in satoshis
|
||||||
|
size: u32, // in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MempoolEntry {
|
||||||
|
pub fn fee_per_byte(&self) -> f32 {
|
||||||
|
self.fee as f32 / self.size as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Daemon {
|
impl Daemon {
|
||||||
pub fn new(addr: &str) -> Daemon {
|
pub fn new(addr: &str) -> Daemon {
|
||||||
Daemon {
|
Daemon {
|
||||||
|
@ -165,6 +176,35 @@ impl Daemon {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getmempooltxids(&self) -> Result<Vec<Sha256dHash>> {
|
||||||
|
let txids: Value = self.request("getrawmempool", json!([/*verbose=*/ false]))?;
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for value in txids.as_array().chain_err(|| "non-array result")? {
|
||||||
|
result.push(parse_hash(&value).chain_err(|| "invalid txid")?);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getmempoolentry(&self, txid: &Sha256dHash) -> Result<MempoolEntry> {
|
||||||
|
let entry = self.request("getmempoolentry", json!([txid.be_hex_string()]))?;
|
||||||
|
let fees = entry
|
||||||
|
.get("fees")
|
||||||
|
.chain_err(|| "missing fees section")?
|
||||||
|
.as_object()
|
||||||
|
.chain_err(|| "non-object fees")?;
|
||||||
|
Ok(MempoolEntry {
|
||||||
|
fee: (fees.get("base")
|
||||||
|
.chain_err(|| "missing base fee")?
|
||||||
|
.as_f64()
|
||||||
|
.chain_err(|| "non-float fee")? * 100_000_000f64) as u64,
|
||||||
|
size: entry
|
||||||
|
.get("size")
|
||||||
|
.chain_err(|| "missing size")?
|
||||||
|
.as_u64()
|
||||||
|
.chain_err(|| "non-integer size")? as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_all_headers(&self) -> Result<HeaderMap> {
|
pub fn get_all_headers(&self) -> Result<HeaderMap> {
|
||||||
let info: Value = self.request("getblockchaininfo", json!([]))?;
|
let info: Value = self.request("getblockchaininfo", json!([]))?;
|
||||||
let max_height = info.get("blocks")
|
let max_height = info.get("blocks")
|
||||||
|
|
17
src/index.rs
17
src/index.rs
|
@ -17,22 +17,7 @@ use time;
|
||||||
|
|
||||||
use daemon::Daemon;
|
use daemon::Daemon;
|
||||||
use store::{Row, Store};
|
use store::{Row, Store};
|
||||||
use types::HeaderMap;
|
use types::{full_hash, hash_prefix, FullHash, HashPrefix, HeaderMap};
|
||||||
|
|
||||||
// TODO: consolidate serialization/deserialize code for bincode/bitcoin.
|
|
||||||
const HASH_LEN: usize = 32;
|
|
||||||
pub const HASH_PREFIX_LEN: usize = 8;
|
|
||||||
|
|
||||||
pub type FullHash = [u8; HASH_LEN];
|
|
||||||
pub type HashPrefix = [u8; HASH_PREFIX_LEN];
|
|
||||||
|
|
||||||
pub fn hash_prefix(hash: &[u8]) -> HashPrefix {
|
|
||||||
array_ref![hash, 0, HASH_PREFIX_LEN].clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_hash(hash: &[u8]) -> FullHash {
|
|
||||||
array_ref![hash, 0, HASH_LEN].clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move to a separate file (to break index<->daemon dependency)
|
// TODO: move to a separate file (to break index<->daemon dependency)
|
||||||
#[derive(Eq, PartialEq, Clone)]
|
#[derive(Eq, PartialEq, Clone)]
|
||||||
|
|
|
@ -26,6 +26,7 @@ extern crate serde_json;
|
||||||
|
|
||||||
pub mod daemon;
|
pub mod daemon;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod mempool;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
58
src/mempool.rs
Normal file
58
src/mempool.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use bitcoin::blockdata::transaction::Transaction;
|
||||||
|
use bitcoin::util::hash::Sha256dHash;
|
||||||
|
use daemon::{Daemon, MempoolEntry};
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
use query::{FundingOutput, SpendingInput};
|
||||||
|
use types::FullHash;
|
||||||
|
|
||||||
|
error_chain!{}
|
||||||
|
|
||||||
|
pub struct Stats {
|
||||||
|
tx: Transaction,
|
||||||
|
entry: MempoolEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tracker<'a> {
|
||||||
|
txids: HashMap<Sha256dHash, Stats>,
|
||||||
|
daemon: &'a Daemon,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Tracker<'a> {
|
||||||
|
pub fn new(daemon: &Daemon) -> Tracker {
|
||||||
|
Tracker {
|
||||||
|
txids: HashMap::new(),
|
||||||
|
daemon: daemon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_from_daemon(&mut self) -> Result<()> {
|
||||||
|
let new_txids = HashSet::<Sha256dHash>::from_iter(self.daemon
|
||||||
|
.getmempooltxids()
|
||||||
|
.chain_err(|| "failed to update mempool from daemon")?);
|
||||||
|
self.txids.retain(|txid, _| new_txids.contains(txid)); // remove old TXNs
|
||||||
|
for txid in new_txids {
|
||||||
|
if let Entry::Vacant(map_entry) = self.txids.entry(txid) {
|
||||||
|
let tx = match self.daemon.gettransaction(&txid) {
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("missing tx {}: {}", txid, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let entry = match self.daemon.getmempoolentry(&txid) {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("no mempool entry {}: {}", txid, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!("new mempool tx: {}, {:.3}", txid, entry.fee_per_byte());
|
||||||
|
map_entry.insert(Stats { tx, entry });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,9 @@ use bitcoin::util::hash::Sha256dHash;
|
||||||
use itertools::enumerate;
|
use itertools::enumerate;
|
||||||
|
|
||||||
use daemon::Daemon;
|
use daemon::Daemon;
|
||||||
use index::{compute_script_hash, hash_prefix, HashPrefix, HeaderEntry, Index, TxInKey, TxInRow,
|
use index::{compute_script_hash, HeaderEntry, Index, TxInKey, TxInRow, TxKey, TxOutRow};
|
||||||
TxKey, TxOutRow, HASH_PREFIX_LEN};
|
|
||||||
use store::Store;
|
use store::Store;
|
||||||
|
use types::{hash_prefix, HashPrefix, HASH_PREFIX_LEN};
|
||||||
|
|
||||||
pub struct Query<'a> {
|
pub struct Query<'a> {
|
||||||
store: &'a Store,
|
store: &'a Store,
|
||||||
|
|
|
@ -12,8 +12,8 @@ use std::io::{BufRead, BufReader, Write};
|
||||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
|
|
||||||
use index::FullHash;
|
|
||||||
use query::{Query, Status};
|
use query::{Query, Status};
|
||||||
|
use types::FullHash;
|
||||||
|
|
||||||
error_chain!{}
|
error_chain!{}
|
||||||
|
|
||||||
|
|
15
src/types.rs
15
src/types.rs
|
@ -5,3 +5,18 @@ pub use bitcoin::util::hash::Sha256dHash;
|
||||||
|
|
||||||
pub type Bytes = Vec<u8>;
|
pub type Bytes = Vec<u8>;
|
||||||
pub type HeaderMap = HashMap<Sha256dHash, BlockHeader>;
|
pub type HeaderMap = HashMap<Sha256dHash, BlockHeader>;
|
||||||
|
|
||||||
|
// TODO: consolidate serialization/deserialize code for bincode/bitcoin.
|
||||||
|
const HASH_LEN: usize = 32;
|
||||||
|
pub const HASH_PREFIX_LEN: usize = 8;
|
||||||
|
|
||||||
|
pub type FullHash = [u8; HASH_LEN];
|
||||||
|
pub type HashPrefix = [u8; HASH_PREFIX_LEN];
|
||||||
|
|
||||||
|
pub fn hash_prefix(hash: &[u8]) -> HashPrefix {
|
||||||
|
array_ref![hash, 0, HASH_PREFIX_LEN].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn full_hash(hash: &[u8]) -> FullHash {
|
||||||
|
array_ref![hash, 0, HASH_LEN].clone()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue