global: fixes

This commit is contained in:
nym21
2026-04-04 11:53:27 +02:00
parent 366ac33e23
commit 8bc993eceb
13 changed files with 194 additions and 95 deletions
+1 -1
View File
@@ -8198,7 +8198,7 @@ pub struct BrkClient {
impl BrkClient {
/// Client version.
pub const VERSION: &'static str = "v0.3.0-alpha.2";
pub const VERSION: &'static str = "v0.3.0-alpha.3";
/// Create a new client with the given base URL.
pub fn new(base_url: impl Into<String>) -> Self {
+106 -41
View File
@@ -4,7 +4,7 @@ use brk_error::{Error, Result};
use brk_reader::Reader;
use brk_types::{
BlkPosition, BlockExtras, BlockHash, BlockHashPrefix, BlockHeader, BlockInfo, BlockInfoV1,
BlockPool, FeeRate, Height, Sats, Timestamp, TxIndex, VSize, pools,
BlockPool, FeeRate, Height, PoolSlug, Sats, Timestamp, TxIndex, VSize, pools,
};
use vecdb::{AnyVec, ReadableVec, VecIndex};
@@ -126,15 +126,15 @@ impl Query {
height: Height::from(begin + i),
version: header.version,
timestamp: timestamps[i],
bits: header.bits,
nonce: header.nonce,
difficulty: *difficulties[i],
merkle_root: header.merkle_root,
tx_count,
size: *sizes[i],
weight: weights[i],
merkle_root: header.merkle_root,
previous_block_hash: header.previous_block_hash,
median_time,
nonce: header.nonce,
bits: header.bits,
difficulty: *difficulties[i],
});
}
@@ -251,14 +251,6 @@ impl Query {
let fa_pct90 = fad.pct90.height.collect_range_at(begin, end);
let fa_max = fad.max.height.collect_range_at(begin, end);
// Bulk read tx positions range covering all coinbase txs (first tx of each block)
let tx_pos_begin = first_tx_indexes[0].to_usize();
let tx_pos_end = first_tx_indexes[count - 1].to_usize() + 1;
let all_tx_positions = indexer
.vecs
.transactions
.position
.collect_range_at(tx_pos_begin, tx_pos_end);
// Bulk read median time window
let median_start = begin.saturating_sub(10);
@@ -293,16 +285,25 @@ impl Query {
let pool_slug = pool_slugs[i];
let pool = all_pools.get(pool_slug);
let varint_len = Self::compact_size_len(tx_count);
let coinbase_offset = HEADER_SIZE as u32 + varint_len;
let coinbase_pos = positions[i] + coinbase_offset;
let coinbase_read_len = size as usize - coinbase_offset as usize;
let (
coinbase_raw,
coinbase_address,
coinbase_addresses,
coinbase_signature,
coinbase_signature_ascii,
) = Self::parse_coinbase_tx(
reader,
all_tx_positions[first_tx_indexes[i].to_usize() - tx_pos_begin],
);
scriptsig_bytes,
) = Self::parse_coinbase_tx(reader, coinbase_pos, coinbase_read_len);
let miner_names = if pool_slug == PoolSlug::Ocean {
Self::parse_datum_miner_names(&scriptsig_bytes)
} else {
None
};
let median_time =
Self::compute_median_time(&median_timestamps, begin + i, median_start);
@@ -312,15 +313,15 @@ impl Query {
height: Height::from(begin + i),
version: header.version,
timestamp: timestamps[i],
bits: header.bits,
nonce: header.nonce,
difficulty: *difficulties[i],
merkle_root: header.merkle_root,
tx_count,
size,
weight,
merkle_root: header.merkle_root,
previous_block_hash: header.previous_block_hash,
median_time,
nonce: header.nonce,
bits: header.bits,
difficulty: *difficulties[i],
};
let total_input_amt = input_volumes[i];
@@ -343,7 +344,7 @@ impl Query {
id: pool.unique_id(),
name: pool.name.to_string(),
slug: pool_slug,
miner_names: None,
miner_names,
},
avg_fee: Sats::from(if non_coinbase > 0 {
total_fees_u64 / non_coinbase
@@ -383,6 +384,8 @@ impl Query {
total_input_amt,
virtual_size: vsize as f64,
price: prices[i],
orphans: vec![],
first_seen: None,
};
blocks.push(BlockInfoV1 { info, extras });
@@ -448,38 +451,94 @@ impl Query {
Timestamp::from(sorted[sorted.len() / 2])
}
fn compact_size_len(tx_count: u32) -> u32 {
if tx_count <= 0xFC {
1
} else if tx_count <= 0xFFFF {
3
} else {
5
}
}
/// Parse OCEAN DATUM protocol miner names from coinbase scriptsig.
/// Skips BIP34 height push, reads tag payload, splits on 0x0F delimiter.
fn parse_datum_miner_names(scriptsig: &[u8]) -> Option<Vec<String>> {
if scriptsig.is_empty() {
return None;
}
// Skip BIP34 height push: first byte is length of height data
let height_len = scriptsig[0] as usize;
let mut tag_len_idx = 1 + height_len;
if tag_len_idx >= scriptsig.len() {
return None;
}
// Read tags payload length (may use OP_PUSHDATA1 for >75 bytes)
let mut tags_len = scriptsig[tag_len_idx] as usize;
if tags_len == 0x4c {
tag_len_idx += 1;
if tag_len_idx >= scriptsig.len() {
return None;
}
tags_len = scriptsig[tag_len_idx] as usize;
}
let tag_start = tag_len_idx + 1;
if tag_start + tags_len > scriptsig.len() {
return None;
}
// Decode tag bytes, strip nulls, split on 0x0F, keep only alphanumeric + space
let tag_bytes = &scriptsig[tag_start..tag_start + tags_len];
let tag_string: String = tag_bytes
.iter()
.filter(|&&b| b != 0x00)
.map(|&b| b as char)
.collect();
let names: Vec<String> = tag_string
.split('\x0f')
.map(|s| {
s.chars()
.filter(|c| c.is_ascii_alphanumeric() || *c == ' ')
.collect::<String>()
})
.filter(|s| !s.trim().is_empty())
.collect();
if names.is_empty() { None } else { Some(names) }
}
fn parse_coinbase_tx(
reader: &Reader,
position: BlkPosition,
) -> (String, Option<String>, Vec<String>, String, String) {
let raw_bytes = match reader.read_raw_bytes(position, 1000) {
len: usize,
) -> (String, Option<String>, Vec<String>, String, String, Vec<u8>) {
let empty = (String::new(), None, vec![], String::new(), String::new(), vec![]);
let raw_bytes = match reader.read_raw_bytes(position, len) {
Ok(bytes) => bytes,
Err(_) => return (String::new(), None, vec![], String::new(), String::new()),
Err(_) => return empty,
};
let tx = match bitcoin::Transaction::consensus_decode(&mut raw_bytes.as_slice()) {
Ok(tx) => tx,
Err(_) => return (String::new(), None, vec![], String::new(), String::new()),
Err(_) => return empty,
};
let coinbase_raw = tx
let scriptsig_bytes: Vec<u8> = tx
.input
.first()
.map(|input| input.script_sig.as_bytes().to_lower_hex_string())
.map(|input| input.script_sig.as_bytes().to_vec())
.unwrap_or_default();
let coinbase_signature_ascii = tx
.input
.first()
.map(|input| {
input
.script_sig
.as_bytes()
.iter()
.map(|&b| b as char)
.collect::<String>()
})
.unwrap_or_default();
let coinbase_raw = scriptsig_bytes.to_lower_hex_string();
let coinbase_signature_ascii: String = scriptsig_bytes
.iter()
.map(|&b| b as char)
.collect();
let coinbase_addresses: Vec<String> = tx
.output
@@ -494,7 +553,12 @@ impl Query {
let coinbase_signature = tx
.output
.first()
.iter()
.find(|output| {
bitcoin::Address::from_script(&output.script_pubkey, bitcoin::Network::Bitcoin)
.is_ok()
})
.or(tx.output.first())
.map(|output| output.script_pubkey.to_asm_string())
.unwrap_or_default();
@@ -504,6 +568,7 @@ impl Query {
coinbase_addresses,
coinbase_signature,
coinbase_signature_ascii,
scriptsig_bytes,
)
}
}
+10 -1
View File
@@ -170,7 +170,16 @@ impl Query {
.collect();
let weight = Weight::from(tx.weight());
let total_sigop_cost = tx.total_sigop_cost(|_| None);
let total_sigop_cost = tx.total_sigop_cost(|outpoint| {
tx.input
.iter()
.position(|i| i.previous_output == *outpoint)
.and_then(|j| input[j].prevout.as_ref())
.map(|p| bitcoin::TxOut {
value: bitcoin::Amount::from_sat(u64::from(p.value)),
script_pubkey: p.script_pubkey.clone(),
})
});
let output: Vec<TxOut> = tx.output.into_iter().map(TxOut::from).collect();
let mut transaction = Transaction {
+7
View File
@@ -107,6 +107,13 @@ pub struct BlockExtras {
#[serde(rename = "virtualSize")]
pub virtual_size: f64,
/// Timestamp when the block was first seen (always null, not yet supported)
#[serde(rename = "firstSeen")]
pub first_seen: Option<u64>,
/// Orphaned blocks (always empty)
pub orphans: Vec<String>,
/// USD price at block height
pub price: Dollars,
}
+8 -8
View File
@@ -14,24 +14,24 @@ pub struct BlockInfo {
pub version: u32,
/// Block timestamp (Unix time)
pub timestamp: Timestamp,
/// Compact target (bits)
pub bits: u32,
/// Nonce
pub nonce: u32,
/// Block difficulty
pub difficulty: f64,
/// Merkle root of the transaction tree
pub merkle_root: String,
/// Number of transactions
pub tx_count: u32,
/// Block size in bytes
pub size: u64,
/// Block weight in weight units
pub weight: Weight,
/// Merkle root of the transaction tree
pub merkle_root: String,
/// Previous block hash
#[serde(rename = "previousblockhash")]
pub previous_block_hash: BlockHash,
/// Median time of the last 11 blocks
#[serde(rename = "mediantime")]
pub median_time: Timestamp,
/// Nonce
pub nonce: u32,
/// Compact target (bits)
pub bits: u32,
/// Block difficulty
pub difficulty: f64,
}
+2 -2
View File
@@ -16,6 +16,6 @@ pub struct BlockPool {
/// URL-friendly pool identifier
pub slug: PoolSlug,
/// Alternative miner names (if identified)
pub miner_names: Option<String>,
/// Miner name tags found in coinbase scriptsig
pub miner_names: Option<Vec<String>>,
}
+8
View File
@@ -31,9 +31,17 @@ pub enum OutputType {
P2PKH,
P2MS,
P2SH,
#[serde(rename = "op_return")]
#[strum(serialize = "op_return")]
OpReturn,
#[serde(rename = "v0_p2wpkh")]
#[strum(serialize = "v0_p2wpkh")]
P2WPKH,
#[serde(rename = "v0_p2wsh")]
#[strum(serialize = "v0_p2wsh")]
P2WSH,
#[serde(rename = "v1_p2tr")]
#[strum(serialize = "v1_p2tr")]
P2TR,
P2A,
Empty,
+5 -5
View File
@@ -10,7 +10,7 @@ pub struct TxIn {
#[schemars(example = "0000000000000000000000000000000000000000000000000000000000000000")]
pub txid: Txid,
/// Output index being spent
/// Output index being spent (u16: coinbase is 65535, mempool.space uses u32: 4294967295)
#[schemars(example = 0)]
pub vout: Vout,
@@ -54,8 +54,8 @@ impl Serialize for TxIn {
let has_witness = !self.witness.is_empty();
let has_scriptsig = !self.script_sig.is_empty();
// P2SH-wrapped SegWit: both scriptsig and witness present
let inner_redeem = if has_scriptsig && has_witness {
// P2SH-wrapped SegWit: both scriptsig and witness present (not coinbase)
let inner_redeem = if has_scriptsig && has_witness && !self.is_coinbase {
self.script_sig
.redeem_script()
.map(|s| s.to_asm_string())
@@ -64,8 +64,8 @@ impl Serialize for TxIn {
String::new()
};
// P2WSH: witness has >2 items, last is the witnessScript
let inner_witness = if has_witness && !has_scriptsig && self.witness.len() > 2 {
// P2WSH / P2SH-P2WSH: witness has >2 items, last is the witnessScript
let inner_witness = if has_witness && self.witness.len() > 2 {
if let Some(last) = self.witness.last() {
let bytes: Vec<u8> = bitcoin::hex::FromHex::from_hex(last).unwrap_or_default();
ScriptBuf::from(bytes).to_asm_string()
+6 -2
View File
@@ -92,11 +92,15 @@ impl Serialize for TxOut {
where
S: Serializer,
{
let mut state = serializer.serialize_struct("TxOut", 5)?;
let addr = self.addr();
let field_count = if addr.is_some() { 5 } else { 4 };
let mut state = serializer.serialize_struct("TxOut", field_count)?;
state.serialize_field("scriptpubkey", &self.script_pubkey.to_hex_string())?;
state.serialize_field("scriptpubkey_asm", &self.script_pubkey_asm())?;
state.serialize_field("scriptpubkey_type", &self.type_())?;
state.serialize_field("scriptpubkey_address", &self.addr())?;
if let Some(addr) = &addr {
state.serialize_field("scriptpubkey_address", addr)?;
}
state.serialize_field("value", &self.value)?;
state.end()
}