use bsv_primitives::chainhash::Hash;
use bsv_primitives::hash::sha256d;
use bsv_primitives::util::{BsvReader, BsvWriter, VarInt};
use crate::input::{TransactionInput, DEFAULT_SEQUENCE_NUMBER};
use crate::output::TransactionOutput;
use crate::sighash;
use crate::TransactionError;
#[derive(Clone, Debug)]
pub struct Transaction {
pub version: u32,
pub inputs: Vec<TransactionInput>,
pub outputs: Vec<TransactionOutput>,
pub lock_time: u32,
}
impl Transaction {
pub fn new() -> Self {
Transaction {
version: 1,
inputs: Vec::new(),
outputs: Vec::new(),
lock_time: 0,
}
}
pub fn from_hex(hex_str: &str) -> Result<Self, TransactionError> {
let bytes = hex::decode(hex_str)
.map_err(|e| TransactionError::SerializationError(format!("invalid hex: {}", e)))?;
Self::from_bytes(&bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, TransactionError> {
let mut reader = BsvReader::new(bytes);
let tx = Self::read_from(&mut reader)?;
if reader.remaining() != 0 {
return Err(TransactionError::SerializationError(format!(
"trailing {} bytes after transaction",
reader.remaining()
)));
}
Ok(tx)
}
pub fn read_from(reader: &mut BsvReader) -> Result<Self, TransactionError> {
let version = reader
.read_u32_le()
.map_err(|e| TransactionError::SerializationError(format!("reading version: {}", e)))?;
let input_count = reader.read_varint().map_err(|e| {
TransactionError::SerializationError(format!("reading input count: {}", e))
})?;
let mut inputs = Vec::with_capacity(input_count.value() as usize);
for _ in 0..input_count.value() {
inputs.push(TransactionInput::read_from(reader)?);
}
let output_count = reader.read_varint().map_err(|e| {
TransactionError::SerializationError(format!("reading output count: {}", e))
})?;
let mut outputs = Vec::with_capacity(output_count.value() as usize);
for _ in 0..output_count.value() {
outputs.push(TransactionOutput::read_from(reader)?);
}
let lock_time = reader.read_u32_le().map_err(|e| {
TransactionError::SerializationError(format!("reading lock time: {}", e))
})?;
Ok(Transaction {
version,
inputs,
outputs,
lock_time,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut writer = BsvWriter::with_capacity(256);
writer.write_u32_le(self.version);
writer.write_varint(VarInt::from(self.inputs.len()));
for input in &self.inputs {
input.write_to(&mut writer);
}
writer.write_varint(VarInt::from(self.outputs.len()));
for output in &self.outputs {
output.write_to(&mut writer);
}
writer.write_u32_le(self.lock_time);
writer.into_bytes()
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn tx_id(&self) -> [u8; 32] {
sha256d(&self.to_bytes())
}
pub fn tx_id_hex(&self) -> String {
let mut id = self.tx_id();
id.reverse();
hex::encode(id)
}
pub fn add_input(&mut self, input: TransactionInput) {
self.inputs.push(input);
}
pub fn input_count(&self) -> usize {
self.inputs.len()
}
pub fn add_output(&mut self, output: TransactionOutput) {
self.outputs.push(output);
}
pub fn output_count(&self) -> usize {
self.outputs.len()
}
pub fn total_output_satoshis(&self) -> u64 {
self.outputs.iter().map(|o| o.satoshis).sum()
}
pub fn total_input_satoshis(&self) -> Result<u64, TransactionError> {
let mut total = 0u64;
for input in &self.inputs {
let sats = input.source_tx_satoshis().ok_or_else(|| {
TransactionError::InvalidTransaction(
"missing source transaction on input".to_string(),
)
})?;
total += sats;
}
Ok(total)
}
pub fn is_coinbase(&self) -> bool {
if self.inputs.len() != 1 {
return false;
}
let input = &self.inputs[0];
if input.source_txid != [0u8; 32] {
return false;
}
input.source_tx_out_index == 0xFFFF_FFFF || input.sequence_number == 0xFFFF_FFFF
}
pub fn size(&self) -> usize {
self.to_bytes().len()
}
pub fn add_input_from(
&mut self,
prev_tx_id: &str,
vout: u32,
prev_locking_script_hex: &str,
satoshis: u64,
) -> Result<(), TransactionError> {
let hash = Hash::from_hex(prev_tx_id)?;
let locking_script = if prev_locking_script_hex.is_empty() {
bsv_script::Script::new()
} else {
bsv_script::Script::from_hex(prev_locking_script_hex)?
};
let mut input = TransactionInput::new();
input.source_txid = *hash.as_bytes();
input.source_tx_out_index = vout;
input.sequence_number = DEFAULT_SEQUENCE_NUMBER;
input.set_source_output(Some(TransactionOutput {
satoshis,
locking_script,
change: false,
}));
self.inputs.push(input);
Ok(())
}
pub fn calc_input_signature_hash(
&self,
input_index: usize,
sighash_flag: u32,
) -> Result<[u8; 32], TransactionError> {
if input_index >= self.inputs.len() {
return Err(TransactionError::InvalidTransaction(format!(
"input index {} out of range (tx has {} inputs)",
input_index,
self.inputs.len()
)));
}
let input = &self.inputs[input_index];
let source_output = input.source_tx_output().ok_or_else(|| {
TransactionError::SigningError(
"missing source output on input (no previous tx info)".to_string(),
)
})?;
let script_bytes = source_output.locking_script.to_bytes();
let satoshis = source_output.satoshis;
sighash::signature_hash(self, input_index, script_bytes, sighash_flag, satoshis)
}
}
impl Default for Transaction {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}