use crate::{
cursor::Cursor,
error::{ParseError, ParseResult},
hash::Hash32,
script::Script,
};
pub const MAX_IO_COUNT: usize = 100_000;
pub const MAX_WITNESS_ITEMS: usize = 500;
pub const MAX_WITNESS_ITEM_SIZE: usize = 520;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutPoint<'a> {
pub txid: Hash32<'a>,
pub vout: u32,
}
impl<'a> OutPoint<'a> {
#[inline]
pub fn parse(c: &mut Cursor<'a>) -> ParseResult<Self> {
let txid = Hash32(c.read_array::<32>()?);
let vout = c.read_u32_le()?;
Ok(Self { txid, vout })
}
#[inline]
pub fn is_coinbase(&self) -> bool {
self.txid.as_bytes().iter().all(|&b| b == 0) && self.vout == 0xffff_ffff
}
}
#[derive(Debug, Clone, Copy)]
pub struct TxInput<'a> {
pub previous_output: OutPoint<'a>,
pub script_sig: Script<'a>,
pub sequence: u32,
}
impl<'a> TxInput<'a> {
#[inline]
pub fn parse(c: &mut Cursor<'a>) -> ParseResult<Self> {
let previous_output = OutPoint::parse(c)?;
let script_sig = Script::parse(c)?;
let sequence = c.read_u32_le()?;
Ok(Self {
previous_output,
script_sig,
sequence,
})
}
#[inline]
pub fn is_coinbase(&self) -> bool {
self.previous_output.is_coinbase()
}
#[inline]
pub fn is_rbf(&self) -> bool {
self.sequence <= 0xffff_fffd
}
}
#[derive(Debug, Clone, Copy)]
pub struct TxOutput<'a> {
pub value: u64,
pub script_pubkey: Script<'a>,
}
impl<'a> TxOutput<'a> {
#[inline]
pub fn parse(c: &mut Cursor<'a>) -> ParseResult<Self> {
let value = c.read_u64_le()?;
let script_pubkey = Script::parse(c)?;
Ok(Self {
value,
script_pubkey,
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Witness<'a> {
pub raw: &'a [u8],
pub item_count: usize,
}
impl<'a> Witness<'a> {
pub(crate) fn parse(data: &'a [u8]) -> ParseResult<(Self, usize)> {
let mut c = Cursor::new(data);
let item_count_u64 = c.read_varint()?;
let item_count: usize =
item_count_u64
.try_into()
.map_err(|_| ParseError::IntegerTooLarge {
value: item_count_u64,
})?;
if item_count > MAX_WITNESS_ITEMS {
return Err(ParseError::OversizedData {
size: item_count,
max: MAX_WITNESS_ITEMS,
});
}
for _ in 0..item_count {
c.read_var_bytes(MAX_WITNESS_ITEM_SIZE)?;
}
let consumed = c.position();
let raw = unsafe { data.get_unchecked(..consumed) };
Ok((Witness { raw, item_count }, consumed))
}
pub fn items(&self) -> WitnessIter<'a> {
let mut skip_cursor = Cursor::new(self.raw);
let _ = skip_cursor.read_varint(); WitnessIter {
cursor: Cursor::new(&self.raw[skip_cursor.position()..]),
remaining: self.item_count,
}
}
}
pub struct WitnessIter<'a> {
cursor: Cursor<'a>,
remaining: usize,
}
impl<'a> Iterator for WitnessIter<'a> {
type Item = ParseResult<&'a [u8]>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
self.remaining -= 1;
Some(self.cursor.read_var_bytes(MAX_WITNESS_ITEM_SIZE))
}
}
#[allow(dead_code)]
pub struct Transaction<'a> {
pub version: i32,
pub is_segwit: bool,
pub inputs: &'a [TxInput<'a>],
pub outputs: &'a [TxOutput<'a>],
pub witnesses: &'a [Witness<'a>],
pub locktime: u32,
pub raw: &'a [u8],
}
impl core::fmt::Debug for Transaction<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Transaction")
.field("version", &self.version)
.field("is_segwit", &self.is_segwit)
.field("input_count", &self.inputs.len())
.field("output_count", &self.outputs.len())
.field("locktime", &self.locktime)
.field("raw_len", &self.raw.len())
.finish()
}
}
pub struct TransactionParser<'a> {
cursor: Cursor<'a>,
}
impl<'a> TransactionParser<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self {
cursor: Cursor::new(data),
}
}
#[inline]
pub fn bytes_consumed(&self) -> usize {
self.cursor.position()
}
pub fn parse_with<FI, FO>(
&mut self,
mut on_input: FI,
mut on_output: FO,
) -> ParseResult<(i32, u32, usize, usize)>
where
FI: FnMut(TxInput<'a>) -> ParseResult<()>,
FO: FnMut(TxOutput<'a>) -> ParseResult<()>,
{
let c = &mut self.cursor;
let version = c.read_i32_le()?;
#[inline]
fn varint_from_first<'a>(c: &mut Cursor<'a>, first: u8) -> ParseResult<u64> {
Ok(match first {
0x00..=0xfc => first as u64,
0xfd => c.read_u16_le()? as u64,
0xfe => c.read_u32_le()? as u64,
0xff => c.read_u64_le()?,
})
}
let first_byte = c.read_u8()?;
let is_segwit = if first_byte == 0x00 {
let flag = c.read_u8()?;
if flag != 0x01 {
return Err(ParseError::InvalidSegwitFlag(flag));
}
true
} else {
false
};
let input_count_u64 = if is_segwit {
c.read_varint()?
} else {
varint_from_first(c, first_byte)?
};
let input_count: usize =
input_count_u64
.try_into()
.map_err(|_| ParseError::IntegerTooLarge {
value: input_count_u64,
})?;
if input_count == 0 {
return Err(ParseError::InvalidInputCount);
}
if input_count > MAX_IO_COUNT {
return Err(ParseError::OversizedData {
size: input_count,
max: MAX_IO_COUNT,
});
}
for _ in 0..input_count {
let input = TxInput::parse(c)?;
on_input(input)?;
}
let output_count_u64 = c.read_varint()?;
let output_count: usize =
output_count_u64
.try_into()
.map_err(|_| ParseError::IntegerTooLarge {
value: output_count_u64,
})?;
if output_count > MAX_IO_COUNT {
return Err(ParseError::OversizedData {
size: output_count,
max: MAX_IO_COUNT,
});
}
for _ in 0..output_count {
let output = TxOutput::parse(c)?;
on_output(output)?;
}
if is_segwit {
for _ in 0..input_count {
let (_, consumed) = Witness::parse(c.as_slice())?;
c.skip(consumed)?;
}
}
let locktime = c.read_u32_le()?;
Ok((version, locktime, input_count, output_count))
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use std::vec::Vec;
fn coinbase_tx_raw() -> Vec<u8> {
let mut tx = Vec::new();
tx.extend_from_slice(&1i32.to_le_bytes());
tx.push(1);
tx.extend_from_slice(&[0u8; 32]);
tx.extend_from_slice(&0xffff_ffffu32.to_le_bytes());
tx.push(4);
tx.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
tx.extend_from_slice(&0xffff_ffffu32.to_le_bytes());
tx.push(1);
tx.extend_from_slice(&(50u64 * 100_000_000).to_le_bytes());
tx.push(0);
tx.extend_from_slice(&0u32.to_le_bytes());
tx
}
#[test]
fn parse_coinbase_streaming() {
let raw = coinbase_tx_raw();
let mut parser = TransactionParser::new(&raw);
let mut inputs = 0usize;
let mut outputs = 0usize;
let mut saw_coinbase = false;
let (version, locktime, in_count, out_count) = parser
.parse_with(
|inp| {
inputs += 1;
if inp.is_coinbase() {
saw_coinbase = true;
}
Ok(())
},
|_out| {
outputs += 1;
Ok(())
},
)
.unwrap();
assert_eq!(version, 1);
assert_eq!(locktime, 0);
assert_eq!(in_count, 1);
assert_eq!(out_count, 1);
assert_eq!(inputs, 1);
assert_eq!(outputs, 1);
assert!(saw_coinbase);
}
#[test]
fn outpoint_coinbase_detection() {
let raw = [0u8; 36];
let mut raw = raw.to_vec();
raw[32..].copy_from_slice(&0xffff_ffffu32.to_le_bytes());
let mut c = Cursor::new(&raw);
let op = OutPoint::parse(&mut c).unwrap();
assert!(op.is_coinbase());
}
}