use crate::{
cursor::Cursor,
error::{ParseError, ParseResult},
hash::Hash32,
transaction::TransactionParser,
};
pub const MAINNET_MAGIC: [u8; 4] = [0xf9, 0xbe, 0xb4, 0xd9];
pub const TESTNET_MAGIC: [u8; 4] = [0x0b, 0x11, 0x09, 0x07];
pub const SIGNET_MAGIC: [u8; 4] = [0x0a, 0x03, 0xcf, 0x40];
pub const MAX_BLOCK_TXN: usize = 1_000_000;
pub const MAX_BLOCK_BYTES: usize = 8_000_000;
#[derive(Debug, Clone, Copy)]
pub struct BlockHeader<'a> {
pub version: i32,
pub prev_block: Hash32<'a>,
pub merkle_root: Hash32<'a>,
pub timestamp: u32,
pub bits: u32,
pub nonce: u32,
pub raw: &'a [u8; 80],
}
impl<'a> BlockHeader<'a> {
pub fn parse(c: &mut Cursor<'a>) -> ParseResult<Self> {
let raw: &'a [u8; 80] = c.read_array::<80>()?;
let mut sub = Cursor::new(raw.as_slice());
let version = sub.read_i32_le()?;
let prev_block = Hash32(sub.read_array::<32>()?);
let merkle_root = Hash32(sub.read_array::<32>()?);
let timestamp = sub.read_u32_le()?;
let bits = sub.read_u32_le()?;
let nonce = sub.read_u32_le()?;
Ok(Self {
version,
prev_block,
merkle_root,
timestamp,
bits,
nonce,
raw,
})
}
#[cfg(feature = "std")]
pub fn block_hash(&self) -> [u8; 32] {
crate::hash::double_sha256(self.raw.as_slice())
}
#[inline]
pub fn difficulty_target(&self) -> (u32, u8) {
let exp = (self.bits >> 24) as u8;
let mantissa = self.bits & 0x00ff_ffff;
(mantissa, exp)
}
}
pub struct BlockTxIter<'a> {
cursor: Cursor<'a>,
total: usize,
consumed: usize,
}
impl<'a> BlockTxIter<'a> {
pub fn new(data: &'a [u8]) -> ParseResult<(BlockHeader<'a>, Self)> {
let mut c = Cursor::new(data);
let header = BlockHeader::parse(&mut c)?;
let tx_count_u64 = c.read_varint()?;
let tx_count: usize = tx_count_u64
.try_into()
.map_err(|_| ParseError::IntegerTooLarge {
value: tx_count_u64,
})?;
if tx_count > MAX_BLOCK_TXN {
return Err(ParseError::OversizedData {
size: tx_count,
max: MAX_BLOCK_TXN,
});
}
Ok((
header,
Self {
cursor: c,
total: tx_count,
consumed: 0,
},
))
}
#[inline]
pub fn total(&self) -> usize {
self.total
}
#[inline]
pub fn consumed(&self) -> usize {
self.consumed
}
#[inline]
pub fn bytes_remaining(&self) -> usize {
self.cursor.remaining()
}
#[inline]
pub fn bytes_consumed(&self) -> usize {
self.cursor.position()
}
pub fn next_tx<FI, FO>(&mut self, on_input: FI, on_output: FO) -> ParseResult<bool>
where
FI: FnMut(crate::transaction::TxInput<'a>) -> ParseResult<()>,
FO: FnMut(crate::transaction::TxOutput<'a>) -> ParseResult<()>,
{
if self.consumed >= self.total {
return Ok(false);
}
let remaining: &'a [u8] = self.cursor.as_slice();
let mut parser = TransactionParser::new(remaining);
parser.parse_with(on_input, on_output)?;
let tx_len = parser.bytes_consumed();
self.cursor.skip(tx_len)?;
self.consumed += 1;
Ok(true)
}
pub fn skip_remaining(&mut self) -> ParseResult<()> {
while self.consumed < self.total {
self.next_tx(|_| Ok(()), |_| Ok(()))?;
}
Ok(())
}
pub fn finish_strict(&self) -> ParseResult<()> {
if self.consumed != self.total {
return Err(ParseError::IncompleteTransactions {
expected: self.total,
parsed: self.consumed,
});
}
let rem = self.cursor.remaining();
if rem != 0 {
return Err(ParseError::TrailingBytes { remaining: rem });
}
Ok(())
}
pub fn consume_all_strict<FI, FO>(
mut self,
mut on_input: FI,
mut on_output: FO,
) -> ParseResult<()>
where
FI: FnMut(crate::transaction::TxInput<'a>) -> ParseResult<()>,
FO: FnMut(crate::transaction::TxOutput<'a>) -> ParseResult<()>,
{
let mut coinbase_txs: usize = 0;
while self.consumed < self.total {
let tx_index = self.consumed;
let mut saw_first_input = false;
let mut is_coinbase_tx = false;
let mut wrapped_on_input = |input: crate::transaction::TxInput<'a>| -> ParseResult<()> {
if !saw_first_input {
saw_first_input = true;
is_coinbase_tx = input.previous_output.is_coinbase();
if is_coinbase_tx {
coinbase_txs += 1;
if tx_index != 0 {
return Err(ParseError::InvalidCoinbaseCount {
count: coinbase_txs,
});
}
}
}
on_input(input)
};
let mut wrapped_on_output =
|output: crate::transaction::TxOutput<'a>| -> ParseResult<()> { on_output(output) };
let _ = self.next_tx(&mut wrapped_on_input, &mut wrapped_on_output)?;
}
self.finish_strict()?;
if coinbase_txs != 1 {
return Err(ParseError::InvalidCoinbaseCount {
count: coinbase_txs,
});
}
Ok(())
}
}
pub struct BlkFileIter<'a> {
cursor: Cursor<'a>,
magic: [u8; 4],
}
impl<'a> BlkFileIter<'a> {
pub fn new(data: &'a [u8], magic: [u8; 4]) -> Self {
Self {
cursor: Cursor::new(data),
magic,
}
}
pub fn next_block(&mut self) -> ParseResult<Option<&'a [u8]>> {
if self.cursor.is_empty() {
return Ok(None);
}
let magic: &[u8; 4] = self.cursor.read_array::<4>()?;
if *magic != self.magic {
return Err(ParseError::MagicMismatch {
expected: self.magic,
got: *magic,
});
}
let size = self.cursor.read_u32_le()? as usize;
if size > MAX_BLOCK_BYTES {
return Err(ParseError::OversizedData {
size,
max: MAX_BLOCK_BYTES,
});
}
let block_bytes = self.cursor.read_bytes(size)?;
Ok(Some(block_bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn genesis_header_raw() -> [u8; 80] {
hex_literal::hex!(
"01000000" "0000000000000000000000000000000000000000000000000000000000000000" "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" "29ab5f49" "ffff001d" "1dac2b7c" )
}
#[test]
fn parse_genesis_header() {
let raw = genesis_header_raw();
let mut cursor = Cursor::new(&raw);
let header = BlockHeader::parse(&mut cursor).unwrap();
assert_eq!(header.version, 1);
assert_eq!(header.nonce, 0x7c2bac1d);
assert!(header.prev_block.as_bytes().iter().all(|&b| b == 0));
}
#[test]
fn header_is_zero_copy() {
let raw = genesis_header_raw();
let raw_ptr = raw.as_ptr();
let mut cursor = Cursor::new(&raw);
let header = BlockHeader::parse(&mut cursor).unwrap();
assert_eq!(header.prev_block.as_bytes().as_ptr(), unsafe {
raw_ptr.add(4)
});
}
}