bcloop 0.1.0

A tool for processing Bitcoin-like blockchain data
Documentation
use std::io::{Cursor, Read, Result, Write};

use serde::{Deserialize, Serialize};

use crate::sha256d;

// https://en.bitcoin.it/wiki/Protocol_documentation

// MARK: Helpers

#[inline(always)]
fn read_u8(cur: &mut Cursor<Vec<u8>>) -> Result<u8> {
  let mut raw = [0u8; 1];
  std::io::Read::read_exact(cur, &mut raw)?;
  Ok(raw[0])
}

#[inline(always)]
fn read_u16(cur: &mut Cursor<Vec<u8>>) -> Result<u16> {
  let mut raw = [0u8; 2];
  std::io::Read::read_exact(cur, &mut raw)?;
  Ok(u16::from_le_bytes(raw))
}

#[inline(always)]
fn read_u32(cur: &mut Cursor<Vec<u8>>) -> Result<u32> {
  let mut raw = [0u8; 4];
  std::io::Read::read_exact(cur, &mut raw)?;
  Ok(u32::from_le_bytes(raw))
}

#[inline(always)]
fn read_u64(cur: &mut Cursor<Vec<u8>>) -> Result<u64> {
  let mut raw = [0u8; 8];
  std::io::Read::read_exact(cur, &mut raw)?;
  Ok(u64::from_le_bytes(raw))
}

fn read_var(cur: &mut Cursor<Vec<u8>>) -> Result<u64> {
  let pre = read_u8(cur)?;
  Ok(match pre {
    0xFD => read_u16(cur)? as u64,
    0xFE => read_u32(cur)? as u64,
    0xFF => read_u64(cur)? as u64,
    _ => pre as u64,
  })
}

#[inline(always)]
fn read_256(cur: &mut Cursor<Vec<u8>>) -> Result<[u8; 32]> {
  let mut hash = [0u8; 32];
  std::io::Read::read_exact(cur, &mut hash)?;
  hash.reverse(); // big endian to little endian
  Ok(hash)
}

#[inline(always)]
fn read_vec(cur: &mut Cursor<Vec<u8>>, len: u64) -> Result<Box<[u8]>> {
  let mut vec = vec![0; len as usize];
  std::io::Read::read_exact(cur, &mut vec)?;
  assert_eq!(len, vec.len() as u64, "read_vec: {} != {}", len, vec.len());
  Ok(vec.into_boxed_slice())
}

// MARK: TxIn

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxIn {
  pub prev_tx: [u8; 32],
  pub prev_idx: u32,
  pub script_len: u64,
  pub script_sig: Box<[u8]>,
  pub sequence: u32,
}

impl TxIn {
  pub fn load(cur: &mut Cursor<Vec<u8>>) -> Result<TxIn> {
    let mut prev_tx = read_256(cur)?;

    let prev_idx = read_u32(cur)?;
    let script_len = read_var(cur)?;
    let script_sig = read_vec(cur, script_len)?;
    let sequence = read_u32(cur)?;
    Ok(TxIn { prev_tx, prev_idx, script_len, script_sig, sequence })
  }
}

// MARK: TxOut

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TxOut {
  pub amount: u64,
  pub script_len: u64,
  pub script_pub: Box<[u8]>,
}

impl TxOut {
  pub fn load(cur: &mut Cursor<Vec<u8>>) -> Result<TxOut> {
    let amount = read_u64(cur)?;
    let script_len = read_var(cur)?;
    let script_pub = read_vec(cur, script_len)?;
    Ok(TxOut { amount, script_len, script_pub })
  }
}

// MARK: Tx

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tx {
  pub txid: [u8; 32], // non blockchain field
  pub segwit: bool,   // non blockchain field
  pub version: u32,
  pub in_count: u64,
  pub inputs: Vec<TxIn>,
  pub out_count: u64,
  pub outputs: Vec<TxOut>,
  pub lock_time: u32,
}

impl Tx {
  pub fn load(cur: &mut Cursor<Vec<u8>>) -> Result<Tx> {
    let pos_beg = cur.position() as usize;
    let version = read_u32(cur)?;

    let mut flags = 0u8;
    let mut in_count = read_var(cur)?;
    if in_count == 0 {
      flags = read_u8(cur)?;
      in_count = read_var(cur)?;
    }

    let mut inputs = Vec::<TxIn>::with_capacity(in_count as usize);
    for _ in 0..in_count {
      inputs.push(TxIn::load(cur)?);
    }

    let out_count = read_var(cur)?;
    let mut outputs = Vec::<TxOut>::with_capacity(out_count as usize);
    for _ in 0..out_count {
      outputs.push(TxOut::load(cur)?);
    }

    let pos_seg = cur.position() as usize;
    let segwit = flags & 1 > 0;

    if segwit {
      for _ in 0..in_count {
        let num_items = read_var(cur)?;
        for _ in 0..num_items {
          let item_len = read_var(cur)?;
          if item_len > 0 {
            let _ = read_vec(cur, item_len)?;
          }
        }
      }
    }

    let lock_time = read_u32(cur)?;
    let pos_end = cur.position() as usize;

    let raw = cur.get_ref();

    let mut txid = if segwit {
      let io_size = pos_seg - pos_beg - 6;
      let mut bin = vec![0u8; 4 + io_size + 4];
      bin[0..4].copy_from_slice(&raw[pos_beg..pos_beg + 4]);
      bin[4..4 + io_size].copy_from_slice(&raw[(pos_beg + 6)..(pos_beg + 6) + io_size]);
      bin[(4 + io_size)..(4 + io_size + 4)].copy_from_slice(&raw[pos_end - 4..pos_end]);
      sha256d(&bin)
    } else {
      let io_size = pos_end - pos_beg;
      let mut bin = vec![0u8; io_size];
      bin.copy_from_slice(&raw[pos_beg..pos_end]);
      sha256d(&bin)
    };

    txid.reverse(); // big endian to little endian

    Ok(Tx { txid, segwit, version, in_count, inputs, out_count, outputs, lock_time })
  }
}

// MARK: Block

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
  pub height: u32,          // non blockchain field
  pub block_hash: [u8; 32], // non blockchain field
  pub version: u32,
  pub prev_block: [u8; 32],
  pub merkle_root: [u8; 32],
  pub timestamp: u32,
  pub bits: u32,
  pub nonce: u32,
  pub tx_count: u64,
  pub txs: Vec<Tx>,
}

impl Block {
  pub fn load(cur: &mut Cursor<Vec<u8>>) -> Result<Block> {
    let version = read_u32(cur)?;
    let prev_block = read_256(cur)?;
    let merkle_root = read_256(cur)?;
    let timestamp = read_u32(cur)?;
    let bits = read_u32(cur)?;
    let nonce = read_u32(cur)?;

    let tx_count = read_var(cur)?;
    let mut txs = Vec::<Tx>::with_capacity(tx_count as usize);
    for i in 0..tx_count {
      txs.push(Tx::load(cur)?);
    }

    Ok(Block {
      version,
      prev_block,
      merkle_root,
      timestamp,
      bits,
      nonce,
      tx_count,
      txs,
      height: 0,
      block_hash: [0u8; 32],
    })
  }
}