bitsplain 0.1.0-alpha.1

Bitsplain helps understand Bitcoin-related binary data
Documentation
use crate::bitcoin::*;
use crate::btc::datatypes::*;
use crate::dsl::{ann, auto};
use crate::nom::combinator::peek;
use crate::nom::multi::{length_count, many_m_n};
use crate::nom::number::complete::{be_u16, be_u8};
use crate::parse::*;
use crate::tree::Tag;
use crate::types::*;
use crate::value::*;

pub fn out_point(s: Span) -> Parsed<OutPoint> {
    let (s, txid) = parse(
        txid,
        ann("Previous transaction", auto())
            .doc("ID of transaction, which contains this input as one of its outputs"),
    )(s)?;
    let (s, vout) = parse(
        uint32,
        ann("Output", auto())
            .doc("Zero-based index pointing to the specific output of previous transaction, which is this input"),
    )(s)?;
    Ok((s, OutPoint { txid, vout }))
}

pub fn tx_out(s: Span) -> Parsed<TxOut> {
    let (s, value) = parse(
        sat,
        ann("Amount", auto()).doc("Amount of BTC being transfered in this output"),
    )(s)?;
    let bm = s.bookmark();
    let (s, script) = parse(script, ann("Script", Value::Nil))(s)?;

    //    s.insert_before(
    s.insert_at(
        &bm,
        ann(
            "Address",
            Value::Addr(Address::from_script(&script, Network::Bitcoin).ok()),
        ),
    );

    let script_type = if script.is_p2pk() {
        "P2PK"
    } else if script.is_p2sh() {
        "P2SH"
    } else if script.is_p2pkh() {
        "P2PKH"
    } else if script.is_v0_p2wsh() {
        "P2WSH"
    } else if script.is_v0_p2wpkh() {
        "P2WPKH"
    } else if script.is_v1_p2tr() {
        "P2TR"
    } else if script.is_op_return() {
        "OP_RETURN"
    } else {
        "NSTD"
    };

    let tx_out = TxOut {
        value: value.sat(),
        script_pubkey: script,
    };

    let s = s.add_tag(Tag {
        label: script_type.to_string(),
        color: None,
        doc: None,
    });

    Ok((s, tx_out))
}

pub fn tx_outs(input: Span) -> Parsed<Vec<TxOut>> {
    let (s, vout_n) = parse(
        varint,
        ann("Outputs", auto()).doc("Number of outputs of this transaction"),
    )(input)?;
    many_m_n(
        vout_n as usize,
        vout_n as usize,
        parse(with("list", "enumerate", tx_out), ann("vout", Value::Nil)),
    )(s)
}

pub fn tx_ins(input: Span) -> Parsed<Vec<TxIn>> {
    let (s, vin_n) = parse(
        varint,
        ann("vin_n", auto()).doc("Number of inputs participating in this transaction"),
    )(input)?;
    many_m_n(
        vin_n as usize,
        vin_n as usize,
        parse(with("list", "enumerate", tx_in), ann("vin", Value::Nil)),
    )(s)
}

pub fn tx_in(input: Span) -> Parsed<TxIn> {
    let (s, out) = parse(
        out_point,
        ann("out_point", |o: &OutPoint| {
            Value::text(format!("{:?}:{}", o.txid, o.vout))
        }),
    )(input)?;
    let (s, scr) = parse(script, ann("script", Value::Nil))(s)?;
    let (s, (seq, _)) = parse(
        alt(uint32, bytes(4u32)),
        ann("Sequence", |(s, bin): &(u32, Vec<u8>)| {
            Value::alt(Value::Num(*s as i128), Value::bytes(bin.clone()))
        }),
    )(s)?;
    Ok((
        s,
        TxIn {
            previous_output: out,
            script_sig: scr,
            sequence: Sequence(seq),
            witness: Witness::new(),
        },
    ))
}

/// Parse Bitcoin transaction.
pub fn tx(s: Span) -> Parsed<Transaction> {
    // let bm1 = s.bookmark();
    let (s, version) = parse(
        int32,
        ann("Version", auto())
            .doc("Version, haha")
            .tag(|v: &_| Tag {
                label: format!("V{v}"),
                color: None,
                doc: None,
            })
            .splain(|v: &_| format!("Version {v}")),
    )(s)?;
    let (s, flags) = peek(be_u16)(s)?;
    let (s, flags) = if flags == 1 {
        parse(be_u16, ann("Flags", auto()))(s)?
    } else {
        (s, 0)
    };
    let bm2 = s.bookmark();
    let (s, vin) = parse(tx_ins, ann("Vins", Value::Nil))(s)?;
    let (s, vout) = parse(tx_outs, ann("Vouts", Value::Nil))(s)?;

    let (s, _witnesses) = if flags == 1 {
        parse(
            length_count(varint, length_count(varint, be_u8)),
            ann("Witnesses", "Witness data"),
        )(s)?
    } else {
        (s, vec![])
    };

    let (s, locktime) = parse(uint32, ann("Locktime", auto()))(s)?;

    let total = vout.iter().fold(0, |acc, v| acc + v.value);
    let tx = Transaction {
        version,
        lock_time: PackedLockTime(locktime),
        input: vin,
        output: vout,
    };
    s.insert_at(
        &bm2,
        ann("Txid", Value::Hash(tx.txid().as_hash()))
            .doc("ID of this transaction as defined pre-segwit"),
    );
    s.insert_at(
        &bm2,
        ann("Wtxid", Value::Hash(tx.wtxid().as_hash())).doc("Segwit-aware ID of this transaction"),
    );
    s.insert_at(&bm2, ann("Size", Value::Size(tx.size() as u64)));
    s.insert_at(&bm2, ann("Vsize", Value::Size(tx.vsize() as u64)));
    s.insert_at(&bm2, ann("Weight", Value::Size(tx.weight() as u64)));
    s.insert_at(
        &bm2,
        ann("Total amount", Value::Sat(Sat::new(total)))
            .doc("Sum of amounts of all outputs of this transaction"),
    );
    Ok((s, tx))
}