mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 22:34:46 -07:00
mempool: use bitcoin projected block, rest is a very simple prediction
This commit is contained in:
@@ -46,9 +46,8 @@ impl Args {
|
||||
Some((k, v)) => (k.to_string(), v.to_string()),
|
||||
None => (
|
||||
rest.to_string(),
|
||||
iter.next().ok_or_else(|| {
|
||||
Error::Parse(format!("--{rest} requires a value"))
|
||||
})?,
|
||||
iter.next()
|
||||
.ok_or_else(|| Error::Parse(format!("--{rest} requires a value")))?,
|
||||
),
|
||||
};
|
||||
match key.as_str() {
|
||||
@@ -75,11 +74,6 @@ impl Args {
|
||||
.next()
|
||||
.ok_or_else(|| Error::Parse("missing selector".into()))?;
|
||||
let paths: Vec<Path> = iter.map(|f| Path::parse(&f)).collect::<Result<_>>()?;
|
||||
if paths.is_empty() {
|
||||
return Err(Error::Parse(
|
||||
"missing field. ask for at least one (e.g. `blk 0 hash`)".into(),
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
selector,
|
||||
paths,
|
||||
@@ -117,9 +111,7 @@ impl Args {
|
||||
.unwrap_or_else(|| self.bitcoin_dir().join(".cookie"));
|
||||
let auth = if cookie.is_file() {
|
||||
Auth::CookieFile(cookie)
|
||||
} else if let (Some(u), Some(p)) =
|
||||
(self.rpcuser.as_deref(), self.rpcpassword.as_deref())
|
||||
{
|
||||
} else if let (Some(u), Some(p)) = (self.rpcuser.as_deref(), self.rpcpassword.as_deref()) {
|
||||
Auth::UserPass(u.to_string(), p.to_string())
|
||||
} else {
|
||||
return Err(Error::Parse(
|
||||
|
||||
@@ -74,6 +74,40 @@ impl<'a> Ctx<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn full(&self) -> Value {
|
||||
let b = self.block;
|
||||
let (size, weight) = self.size_and_weight();
|
||||
let tx: Vec<Value> = b
|
||||
.txdata
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, tx)| tx_to_value(tx, i == 0))
|
||||
.collect();
|
||||
json!({
|
||||
"height": *b.height(),
|
||||
"hash": b.hash().to_string(),
|
||||
"version": b.header.version.to_consensus(),
|
||||
"version_hex": format!("{:08x}", b.header.version.to_consensus() as u32),
|
||||
"merkle": b.header.merkle_root.to_string(),
|
||||
"time": b.header.time,
|
||||
"nonce": b.header.nonce,
|
||||
"bits": b.header.bits.to_consensus(),
|
||||
"difficulty": b.header.difficulty_float(),
|
||||
"prev": b.header.prev_blockhash.to_string(),
|
||||
"txs": b.txdata.len(),
|
||||
"n_inputs": b.txdata.iter().map(|t| t.input.len()).sum::<usize>(),
|
||||
"n_outputs": b.txdata.iter().map(|t| t.output.len()).sum::<usize>(),
|
||||
"witness_txs": b.txdata.iter().filter(|t| tx_has_witness(t)).count(),
|
||||
"size": size,
|
||||
"strippedsize": (weight - size) / 3,
|
||||
"weight": weight,
|
||||
"subsidy": subsidy_sats(*b.height()),
|
||||
"coinbase": b.coinbase_tag().as_str(),
|
||||
"header_hex": serialize_hex(&b.header),
|
||||
"tx": tx,
|
||||
})
|
||||
}
|
||||
|
||||
fn size_and_weight(&self) -> (usize, usize) {
|
||||
*self
|
||||
.size_weight
|
||||
|
||||
@@ -36,13 +36,20 @@ impl Formatter {
|
||||
row.push('\t');
|
||||
}
|
||||
for c in ctx.resolve_str(path)?.chars() {
|
||||
row.push(if matches!(c, '\t' | '\n' | '\r') { ' ' } else { c });
|
||||
row.push(if matches!(c, '\t' | '\n' | '\r') {
|
||||
' '
|
||||
} else {
|
||||
c
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
fn object(&self, ctx: &Ctx) -> Result<Value> {
|
||||
if self.fields.is_empty() {
|
||||
return Ok(ctx.full());
|
||||
}
|
||||
let mut obj = Map::with_capacity(self.fields.len());
|
||||
for path in &self.fields {
|
||||
obj.insert(path.raw.clone(), ctx.resolve(path)?);
|
||||
@@ -50,4 +57,3 @@ impl Formatter {
|
||||
Ok(Value::Object(obj))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,11 @@ fn run() -> Result<()> {
|
||||
let mode = Mode::pick(args.pretty, args.compact, args.paths.len());
|
||||
let reader = Reader::new(args.blocks_dir(), &client);
|
||||
let formatter = Formatter::new(mode, args.paths);
|
||||
for block in reader.range(start, end)?.iter() {
|
||||
let parser_threads = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(2)
|
||||
/ 2;
|
||||
for block in reader.range_with(start, end, parser_threads)?.iter() {
|
||||
let block = block?;
|
||||
let line = formatter.format(&Ctx::new(&block))?;
|
||||
if !line.is_empty() {
|
||||
|
||||
@@ -10,6 +10,8 @@ impl Mode {
|
||||
pub fn pick(pretty: bool, compact: bool, n_fields: usize) -> Self {
|
||||
if pretty {
|
||||
Self::Pretty
|
||||
} else if n_fields == 0 {
|
||||
Self::Json
|
||||
} else if n_fields == 1 {
|
||||
Self::Bare
|
||||
} else if compact {
|
||||
|
||||
@@ -14,7 +14,9 @@ impl Selector {
|
||||
}
|
||||
};
|
||||
if end < start {
|
||||
return Err(Error::Parse(format!("range end {end} before start {start}")));
|
||||
return Err(Error::Parse(format!(
|
||||
"range end {end} before start {start}"
|
||||
)));
|
||||
}
|
||||
Ok((start, end))
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@ pub fn print() {
|
||||
|
||||
section("USAGE");
|
||||
println!(
|
||||
" blk {} {} [field ...] [OPTIONS]",
|
||||
" blk {} [{} ...] [OPTIONS]",
|
||||
"<selector>".bright_black(),
|
||||
"<field>".bright_black()
|
||||
);
|
||||
println!(
|
||||
" {}",
|
||||
"no fields = full block as JSON (analog of `bitcoin-cli getblock <hash> 2`)".bright_black()
|
||||
);
|
||||
println!();
|
||||
|
||||
section("SELECTOR");
|
||||
@@ -28,8 +32,7 @@ pub fn print() {
|
||||
section("FIELDS");
|
||||
println!(
|
||||
" {}",
|
||||
"dotted paths drill into nested data; omit an index for arrays"
|
||||
.bright_black()
|
||||
"dotted paths drill into nested data; omit an index for arrays".bright_black()
|
||||
);
|
||||
println!();
|
||||
group("block");
|
||||
@@ -58,29 +61,48 @@ pub fn print() {
|
||||
println!();
|
||||
println!(
|
||||
" {}",
|
||||
"Naked tx / tx.i / vin / vout returns the whole sub-object as JSON."
|
||||
.bright_black()
|
||||
"Naked tx / tx.i / vin / vout returns the whole sub-object as JSON.".bright_black()
|
||||
);
|
||||
println!();
|
||||
|
||||
section("OUTPUT");
|
||||
out("no fields", "full block JSON object, one per line (NDJSON)");
|
||||
out("1 field", "bare value, one per line");
|
||||
out("2+ fields", "compact JSON object, one per line (NDJSON)");
|
||||
out("-p, --pretty", "pretty JSON object instead");
|
||||
out("-c, --compact", "tab-separated values, no field names (TSV)");
|
||||
out(
|
||||
"-c, --compact",
|
||||
"tab-separated values, no field names (TSV)",
|
||||
);
|
||||
println!();
|
||||
|
||||
section("OPTIONS");
|
||||
opt("--bitcoindir", "<PATH>", "Bitcoin directory", Some("[OS default]"));
|
||||
opt("--blocksdir", "<PATH>", "Blocks directory", Some("[<bitcoindir>/blocks]"));
|
||||
opt(
|
||||
"--bitcoindir",
|
||||
"<PATH>",
|
||||
"Bitcoin directory",
|
||||
Some("[OS default]"),
|
||||
);
|
||||
opt(
|
||||
"--blocksdir",
|
||||
"<PATH>",
|
||||
"Blocks directory",
|
||||
Some("[<bitcoindir>/blocks]"),
|
||||
);
|
||||
opt("--rpcconnect", "<IP>", "RPC host", Some("[localhost]"));
|
||||
opt("--rpcport", "<PORT>", "RPC port", Some("[8332]"));
|
||||
opt("--rpccookiefile", "<PATH>", "RPC cookie file", Some("[<bitcoindir>/.cookie]"));
|
||||
opt(
|
||||
"--rpccookiefile",
|
||||
"<PATH>",
|
||||
"RPC cookie file",
|
||||
Some("[<bitcoindir>/.cookie]"),
|
||||
);
|
||||
opt("--rpcuser", "<USERNAME>", "RPC username", None);
|
||||
opt("--rpcpassword", "<PASSWORD>", "RPC password", None);
|
||||
println!();
|
||||
|
||||
section("EXAMPLES");
|
||||
ex("blk 800000", "full block as JSON");
|
||||
ex("blk 800000 hash", "bare hash");
|
||||
ex("blk 800000 height hash time", "one compact JSON line");
|
||||
ex("blk 800000 tx.0.txid", "coinbase txid");
|
||||
@@ -128,7 +150,11 @@ fn sel(token: &str, desc: &str) {
|
||||
}
|
||||
|
||||
fn out(label: &str, desc: &str) {
|
||||
println!(" {label}{}{}{desc}", pad(label, LABEL_W), " ".repeat(GAP));
|
||||
println!(
|
||||
" {label}{}{}{desc}",
|
||||
pad(label, LABEL_W),
|
||||
" ".repeat(GAP)
|
||||
);
|
||||
}
|
||||
|
||||
fn opt(flag: &str, ph: &str, desc: &str, default: Option<&str>) {
|
||||
|
||||
Reference in New Issue
Block a user