use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{OpReturnKind, Sats, VSize};
use vecdb::{AnyVec, Exit, ReadableVec, VecIndex};
use super::{Vecs, vecs::Totals};
use crate::transactions;
const KIND_COUNT: usize = OpReturnKind::Unknown as usize + 1;
const OLD_STANDARD_MAX_POST_OP_RETURN_BYTES: u64 = 82;
const WRITE_INTERVAL: usize = 10_000;
#[derive(Clone, Copy, Default)]
struct PolicyTotals {
pre_v30_standard: Totals,
pre_v30_nonstandard: Totals,
oversized: Totals,
multiple: Totals,
}
#[derive(Clone, Copy, Default)]
struct Carrier {
kinds: u32,
output_count: u64,
data_bytes: u64,
oversized_output_count: u64,
oversized_data_bytes: u64,
vsize: VSize,
fees: Sats,
}
impl Carrier {
fn add_output(&mut self, kind: OpReturnKind, data_bytes: u64) {
self.kinds |= kind_bit(kind);
self.output_count += 1;
self.data_bytes += data_bytes;
if data_bytes > OLD_STANDARD_MAX_POST_OP_RETURN_BYTES {
self.oversized_output_count += 1;
self.oversized_data_bytes += data_bytes;
}
}
}
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
fees: &transactions::FeesVecs,
exit: &Exit,
) -> Result<()> {
self.db.sync_bg_tasks()?;
let starting_lengths = indexer.safe_lengths();
let raw = &indexer.vecs.op_return;
let txs = &indexer.vecs.transactions;
let version = raw.first_index.version()
+ raw.to_tx_index.version()
+ raw.kind.version()
+ raw.post_op_return_bytes.version()
+ txs.weight.version()
+ fees.fee.tx_index.version();
self.validate_and_truncate(version, starting_lengths.height)?;
let skip = self.min_len();
let end = raw.first_index.len();
if skip < end {
self.truncate_if_needed_at(skip)?;
let op_return_len = raw.to_tx_index.len();
let mut tx_cursor = raw.to_tx_index.cursor();
let mut kind_cursor = raw.kind.cursor();
let mut post_op_return_bytes = raw.post_op_return_bytes.cursor();
let mut first_index_cursor = raw.first_index.cursor();
let mut weight_cursor = txs.weight.cursor();
let mut fee_cursor = fees.fee.tx_index.cursor();
first_index_cursor.advance(skip);
let mut start = first_index_cursor.next().unwrap().to_usize();
for height in skip..end {
let block_end = if height + 1 < end {
first_index_cursor.next().unwrap().to_usize()
} else {
op_return_len
};
tx_cursor.advance(start - tx_cursor.position());
kind_cursor.advance(start - kind_cursor.position());
post_op_return_bytes.advance(start - post_op_return_bytes.position());
let mut total = Totals::default();
let mut by_kind = [Totals::default(); KIND_COUNT];
let mut policy = PolicyTotals::default();
let mut current_tx = None;
let mut carrier = Carrier::default();
for _ in start..block_end {
let tx_index = tx_cursor.next().unwrap();
let kind = kind_cursor.next().unwrap();
let bytes = u32::from(post_op_return_bytes.next().unwrap()) as u64;
let kind_index = kind as usize;
if current_tx != Some(tx_index) {
finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier);
current_tx = Some(tx_index);
carrier = Carrier::default();
let tx_position = tx_index.to_usize();
weight_cursor.advance(tx_position - weight_cursor.position());
carrier.vsize = VSize::from(weight_cursor.next().unwrap());
fee_cursor.advance(tx_position - fee_cursor.position());
carrier.fees = fee_cursor.next().unwrap();
}
total.data_bytes += bytes;
by_kind[kind_index].output_count += 1;
by_kind[kind_index].data_bytes += bytes;
carrier.add_output(kind, bytes);
}
finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier);
self.total.push(total);
for (kind, metrics) in self.by_kind.iter_typed_mut() {
metrics.push(by_kind[kind as usize]);
}
self.policy.pre_v30_standard.push(policy.pre_v30_standard);
self.policy
.pre_v30_nonstandard
.push(policy.pre_v30_nonstandard);
self.policy.oversized.push(policy.oversized);
self.policy.multiple.push(policy.multiple);
if (height + 1).is_multiple_of(WRITE_INTERVAL) {
let _lock = exit.lock();
self.write()?;
}
start = block_end;
}
let _lock = exit.lock();
self.write()?;
}
let exit = exit.clone();
self.db.run_bg(move |db| {
let _lock = exit.lock();
db.compact_deferred_default()
});
Ok(())
}
}
fn finalize_transaction(
total: &mut Totals,
by_kind: &mut [Totals; KIND_COUNT],
policy: &mut PolicyTotals,
carrier: Carrier,
) {
if carrier.output_count == 0 {
return;
}
add_carrier(total, carrier);
let mut kinds = carrier.kinds;
while kinds != 0 {
let kind_index = kinds.trailing_zeros() as usize;
add_carrier(&mut by_kind[kind_index], carrier);
kinds &= kinds - 1;
}
if carrier.oversized_output_count > 0 {
policy.oversized.output_count += carrier.oversized_output_count;
policy.oversized.data_bytes += carrier.oversized_data_bytes;
add_carrier(&mut policy.oversized, carrier);
}
if carrier.output_count > 1 {
policy.multiple.output_count += carrier.output_count;
policy.multiple.data_bytes += carrier.data_bytes;
add_carrier(&mut policy.multiple, carrier);
}
if carrier.oversized_output_count > 0 || carrier.output_count > 1 {
policy.pre_v30_nonstandard.output_count += carrier.output_count;
policy.pre_v30_nonstandard.data_bytes += carrier.data_bytes;
add_carrier(&mut policy.pre_v30_nonstandard, carrier);
} else {
policy.pre_v30_standard.output_count += carrier.output_count;
policy.pre_v30_standard.data_bytes += carrier.data_bytes;
add_carrier(&mut policy.pre_v30_standard, carrier);
}
}
fn add_carrier(metrics: &mut Totals, carrier: Carrier) {
metrics.tx_count += 1;
metrics.tx_vsize += carrier.vsize;
metrics.fees += carrier.fees;
}
const fn kind_bit(kind: OpReturnKind) -> u32 {
1_u32 << kind as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multiple_kinds_count_one_total_carrier() {
let mut total = Totals::default();
let mut by_kind = [Totals::default(); KIND_COUNT];
let mut policy = PolicyTotals::default();
let mut carrier = Carrier {
vsize: VSize::new(100),
fees: Sats::new(500),
..Carrier::default()
};
carrier.add_output(OpReturnKind::Runes, 15);
carrier.add_output(OpReturnKind::Omni, 15);
finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier);
assert_eq!(total.tx_count, 1);
assert_eq!(by_kind[OpReturnKind::Runes as usize].tx_count, 1);
assert_eq!(by_kind[OpReturnKind::Omni as usize].tx_count, 1);
assert_eq!(total.fees, Sats::new(500));
assert_eq!(by_kind[OpReturnKind::Runes as usize].fees, Sats::new(500));
assert_eq!(by_kind[OpReturnKind::Omni as usize].fees, Sats::new(500));
assert_eq!(policy.multiple.fees, Sats::new(500));
assert_eq!(policy.pre_v30_nonstandard.fees, Sats::new(500));
assert_eq!(policy.multiple.output_count, 2);
assert_eq!(policy.pre_v30_nonstandard.tx_count, 1);
assert_eq!(policy.oversized.tx_count, 0);
assert_eq!(policy.pre_v30_standard.tx_count, 0);
}
#[test]
fn oversized_output_marks_pre_v30_nonstandard_once() {
let mut total = Totals::default();
let mut by_kind = [Totals::default(); KIND_COUNT];
let mut policy = PolicyTotals::default();
let mut carrier = Carrier {
vsize: VSize::new(120),
..Carrier::default()
};
carrier.add_output(OpReturnKind::Unknown, 83);
finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier);
assert_eq!(policy.oversized.output_count, 1);
assert_eq!(policy.oversized.tx_vsize, VSize::new(120));
assert_eq!(policy.pre_v30_nonstandard.tx_count, 1);
assert_eq!(policy.multiple.tx_count, 0);
assert_eq!(policy.pre_v30_standard.tx_count, 0);
}
#[test]
fn standard_output_is_recorded_directly() {
let mut total = Totals::default();
let mut by_kind = [Totals::default(); KIND_COUNT];
let mut policy = PolicyTotals::default();
let mut carrier = Carrier {
vsize: VSize::new(100),
..Carrier::default()
};
carrier.add_output(OpReturnKind::Runes, 15);
finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier);
assert_eq!(policy.pre_v30_standard.output_count, 1);
assert_eq!(policy.pre_v30_standard.data_bytes, 15);
assert_eq!(policy.pre_v30_standard.tx_count, 1);
assert_eq!(policy.pre_v30_standard.tx_vsize, VSize::new(100));
assert_eq!(policy.pre_v30_nonstandard.tx_count, 0);
}
}