use core::{num::NonZeroU32, ops::ControlFlow};
use crate::{
bsl::{TxIns, TxOuts, Witnesses},
number::{read_i32, read_u32, read_u8},
Error, ParseResult, SResult, Visit, Visitor,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transaction<'a> {
slice: &'a [u8],
inputs_outputs_len: Option<NonZeroU32>,
}
impl<'a> Visit<'a> for Transaction<'a> {
#[inline(always)]
fn visit<'b, V: Visitor>(slice: &'a [u8], visit: &'b mut V) -> SResult<'a, Self> {
let _version = read_i32(slice)?;
let inputs = TxIns::visit(&slice[4..], visit)?;
if inputs.parsed().is_empty() {
let segwit_flag = read_u8(inputs.remaining())?;
if segwit_flag == 1 {
let inputs = TxIns::visit(&inputs.remaining()[1..], visit)?;
let outputs = TxOuts::visit(inputs.remaining(), visit)?;
let witnesses = Witnesses::visit(outputs.remaining(), inputs.parsed().n(), visit)?;
if !inputs.parsed().is_empty() && witnesses.parsed().all_empty() {
return Err(Error::SegwitFlagWithoutWitnesses);
}
let _locktime = read_u32(witnesses.remaining())?;
let consumed = 10 + inputs.consumed() + outputs.consumed() + witnesses.consumed();
let inputs_outputs_len =
inputs.parsed().as_ref().len() + outputs.parsed().as_ref().len();
let tx = Transaction {
slice: &slice[..consumed],
inputs_outputs_len: NonZeroU32::new(inputs_outputs_len as u32), };
match visit.visit_transaction(&tx) {
ControlFlow::Continue(_) => Ok(ParseResult::new(&slice[consumed..], tx)),
ControlFlow::Break(_) => Err(Error::VisitBreak),
}
} else {
Err(Error::UnknownSegwitFlag(segwit_flag))
}
} else {
let outputs = TxOuts::visit(inputs.remaining(), visit)?;
let _locktime = read_u32(outputs.remaining())?;
let consumed = inputs.consumed() + outputs.consumed() + 8;
let tx = Transaction {
slice: &slice[..consumed],
inputs_outputs_len: None,
};
match visit.visit_transaction(&tx) {
ControlFlow::Continue(_) => Ok(ParseResult::new(&slice[consumed..], tx)),
ControlFlow::Break(_) => Err(Error::VisitBreak),
}
}
}
}
impl<'a> Transaction<'a> {
pub fn version(&self) -> i32 {
read_i32(&self.slice[..4]).expect("slice length granted during parsing")
}
pub fn locktime(&self) -> u32 {
let from = self.slice.len() - 4; read_u32(&self.slice[from..]).expect("slice length granted during parsing")
}
pub fn txid_preimage(&self) -> (&'a [u8], &'a [u8], &'a [u8]) {
if let Some(len) = self.inputs_outputs_len.as_ref() {
(
&self.slice[..4], &self.slice[6..len.get() as usize + 6], &self.slice[self.as_ref().len() - 4..], )
} else {
(self.slice, &[], &[])
}
}
#[cfg(feature = "bitcoin_hashes")]
pub fn txid(&self) -> crate::bitcoin_hashes::sha256d::Hash {
use crate::bitcoin_hashes::{sha256d, Hash, HashEngine};
let (a, b, c) = self.txid_preimage();
let mut engine = sha256d::Hash::engine();
engine.input(a);
engine.input(b);
engine.input(c);
sha256d::Hash::from_engine(engine)
}
#[cfg(feature = "sha2")]
pub fn txid_sha2(
&self,
) -> crate::sha2::digest::generic_array::GenericArray<u8, crate::sha2::digest::typenum::U32>
{
use crate::sha2::{Digest, Sha256};
let (a, b, c) = self.txid_preimage();
let mut hasher = Sha256::new();
hasher.update(a);
hasher.update(b);
hasher.update(c);
let hash = hasher.finalize();
Sha256::digest(&hash[..])
}
pub fn weight(&self) -> u64 {
let total_size = self.as_ref().len() as u64;
match self.inputs_outputs_len {
Some(n) => {
let base_size = n.get() as u64 + 4 + 4; base_size * 3 + total_size
}
None => total_size * 4,
}
}
}
impl<'a> AsRef<[u8]> for Transaction<'a> {
fn as_ref(&self) -> &[u8] {
self.slice
}
}
#[cfg(feature = "redb")]
impl<'o> redb::RedbValue for Transaction<'o> {
type SelfType<'a> = Transaction<'a> where Self: 'a;
type AsBytes<'a> = &'a [u8] where Self: 'a;
fn fixed_width() -> Option<usize> {
None
}
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
where
Self: 'a,
{
use crate::visit::Parse;
Transaction::parse(data)
.expect("inserted data is not a Transaction")
.parsed_owned()
}
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
where
Self: 'a,
Self: 'b,
{
value.as_ref()
}
fn type_name() -> redb::TypeName {
redb::TypeName::new("bsl::Transaction")
}
}
#[cfg(test)]
mod test {
use crate::{bsl::Transaction, test_common::GENESIS_TX, Parse};
use bitcoin::consensus::deserialize;
use hex_lit::hex;
#[test]
fn parse_genesis_transaction() {
let tx = Transaction::parse(&GENESIS_TX[..]).unwrap();
assert_eq!(tx.remaining(), &[][..]);
assert_eq!(tx.parsed().as_ref(), &GENESIS_TX[..]);
assert_eq!(tx.consumed(), 204);
assert_eq!(tx.parsed().version(), 1);
assert_eq!(tx.parsed().locktime(), 0);
check_hash(
tx.parsed(),
hex!("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"),
);
}
#[test]
fn parse_segwit_transaction() {
let segwit_tx = hex!("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603da1b0e00045503bd5704c7dd8a0d0ced13bb5785010800000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02b4e5a212000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9edf91c46b49eb8a29089980f02ee6b57e7d63d33b18b4fddac2bcd7db2a39837040120000000000000000000000000000000000000000000000000000000000000000000000000");
let tx = Transaction::parse(&segwit_tx[..]).unwrap();
assert_eq!(tx.remaining(), &[]);
assert_eq!(tx.parsed().as_ref(), &segwit_tx[..]);
assert_eq!(tx.consumed(), 222);
assert_eq!(tx.parsed().version(), 1);
assert_eq!(tx.parsed().locktime(), 0);
check_hash(
tx.parsed(),
hex!("4be105f158ea44aec57bf12c5817d073a712ab131df6f37786872cfc70734188"), );
}
#[test]
fn parse_nonminimal_transaction() {
let first_part = hex!("020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff310349ce0b04db6fd2632f466f756e6472792055534120506f6f6c202364726f70676f6c642f1e284d6da44c000000000000ffffffff02311b662500000000");
let varint_nonminimal = hex!("fd1600");
let varint_minimal = hex!("16");
let last_part = hex!("001435f6de260c9f3bdee47524c473a6016c0c055cb90000000000000000266a24aa21a9edd86201e9d314d373d739d7e897c2f369d6cd89ad37902dc3e2202563159e449c0120000000000000000000000000000000000000000000000000000000000000000000000000");
let mut tx_nonminimal = vec![];
tx_nonminimal.extend(first_part);
tx_nonminimal.extend(varint_nonminimal);
tx_nonminimal.extend(last_part);
let mut tx = vec![];
tx.extend(first_part);
tx.extend(varint_minimal);
tx.extend(last_part);
assert_ne!(tx, tx_nonminimal);
assert!(deserialize::<bitcoin::Transaction>(&tx).is_ok());
assert!(deserialize::<bitcoin::Transaction>(&tx_nonminimal).is_err());
assert!(Transaction::parse(&tx[..]).is_ok());
assert!(Transaction::parse(&tx_nonminimal[..]).is_err());
}
#[cfg(target_pointer_width = "64")]
#[test]
fn size_of() {
assert_eq!(std::mem::size_of::<Transaction>(), 24);
}
#[cfg(all(not(feature = "sha2"), not(feature = "bitcoin_hashes")))]
fn check_hash(_tx: &Transaction, _expected: [u8; 32]) {}
#[cfg(all(not(feature = "sha2"), feature = "bitcoin_hashes"))]
fn check_hash(tx: &Transaction, expected: [u8; 32]) {
use crate::test_common::reverse;
assert_eq!(&tx.txid()[..], &reverse(expected)[..]);
}
#[cfg(all(feature = "sha2", not(feature = "bitcoin_hashes")))]
fn check_hash(tx: &Transaction, expected: [u8; 32]) {
use crate::test_common::reverse;
assert_eq!(&tx.txid_sha2()[..], &reverse(expected)[..]);
}
#[cfg(all(feature = "sha2", feature = "bitcoin_hashes"))]
fn check_hash(tx: &Transaction, expected: [u8; 32]) {
use crate::test_common::reverse;
assert_eq!(&tx.txid()[..], &reverse(expected)[..]);
assert_eq!(&tx.txid_sha2()[..], &reverse(expected)[..]);
}
#[cfg(feature = "bitcoin")]
#[test]
fn test_weight() {
fn check_weight(tx_bytes: &[u8]) {
let tx = Transaction::parse(&tx_bytes[..]).unwrap().parsed_owned();
let bitcoin_tx: bitcoin::Transaction =
bitcoin::consensus::deserialize(&tx_bytes[..]).unwrap();
assert_eq!(tx.weight(), bitcoin_tx.weight().to_wu());
}
check_weight(&GENESIS_TX[..]);
let segwit_tx = hex!("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3603da1b0e00045503bd5704c7dd8a0d0ced13bb5785010800000000000a636b706f6f6c122f4e696e6a61506f6f6c2f5345475749542fffffffff02b4e5a212000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9edf91c46b49eb8a29089980f02ee6b57e7d63d33b18b4fddac2bcd7db2a39837040120000000000000000000000000000000000000000000000000000000000000000000000000");
check_weight(&segwit_tx);
}
}