use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::fmt::Debug;
use std::io::{self, Read, Write};
use crate::{
legacy::{Script, TransparentAddress},
transaction::TxId,
};
use super::amount::{Amount, BalanceError, NonNegativeAmount};
pub mod builder;
pub trait Authorization: Debug {
type ScriptSig: Debug + Clone + PartialEq;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Authorized;
impl Authorization for Authorized {
type ScriptSig = Script;
}
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_script_sig(&self, s: A::ScriptSig) -> B::ScriptSig;
fn map_authorization(&self, s: A) -> B;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Bundle<A: Authorization> {
pub vin: Vec<TxIn<A>>,
pub vout: Vec<TxOut>,
pub authorization: A,
}
impl<A: Authorization> Bundle<A> {
pub fn is_coinbase(&self) -> bool {
matches!(&self.vin[..], [input] if input.prevout.is_null())
}
pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
Bundle {
vin: self
.vin
.into_iter()
.map(|txin| TxIn {
prevout: txin.prevout,
script_sig: f.map_script_sig(txin.script_sig),
sequence: txin.sequence,
})
.collect(),
vout: self.vout,
authorization: f.map_authorization(self.authorization),
}
}
pub fn value_balance<E, F>(&self, mut get_prevout_value: F) -> Result<Amount, E>
where
E: From<BalanceError>,
F: FnMut(&OutPoint) -> Result<Amount, E>,
{
let input_sum = self.vin.iter().try_fold(Amount::zero(), |total, txin| {
get_prevout_value(&txin.prevout)
.and_then(|v| (total + v).ok_or_else(|| BalanceError::Overflow.into()))
})?;
let output_sum = self
.vout
.iter()
.map(|p| Amount::from(p.value))
.sum::<Option<Amount>>()
.ok_or(BalanceError::Overflow)?;
(input_sum - output_sum).ok_or_else(|| BalanceError::Underflow.into())
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OutPoint {
hash: TxId,
n: u32,
}
impl OutPoint {
pub fn new(hash: [u8; 32], n: u32) -> Self {
OutPoint {
hash: TxId::from_bytes(hash),
n,
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub const fn fake() -> Self {
OutPoint {
hash: TxId([1u8; 32]),
n: 1,
}
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut hash = [0u8; 32];
reader.read_exact(&mut hash)?;
let n = reader.read_u32::<LittleEndian>()?;
Ok(OutPoint::new(hash, n))
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(self.hash.as_ref())?;
writer.write_u32::<LittleEndian>(self.n)
}
fn is_null(&self) -> bool {
self.hash.as_ref() == &[0; 32] && self.n == u32::MAX
}
pub fn n(&self) -> u32 {
self.n
}
pub fn hash(&self) -> &[u8; 32] {
self.hash.as_ref()
}
pub fn txid(&self) -> &TxId {
&self.hash
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TxIn<A: Authorization> {
pub prevout: OutPoint,
pub script_sig: A::ScriptSig,
pub sequence: u32,
}
impl TxIn<Authorized> {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let prevout = OutPoint::read(&mut reader)?;
let script_sig = Script::read(&mut reader)?;
let sequence = reader.read_u32::<LittleEndian>()?;
Ok(TxIn {
prevout,
script_sig,
sequence,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.prevout.write(&mut writer)?;
self.script_sig.write(&mut writer)?;
writer.write_u32::<LittleEndian>(self.sequence)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxOut {
pub value: NonNegativeAmount,
pub script_pubkey: Script,
}
impl TxOut {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let value = {
let mut tmp = [0u8; 8];
reader.read_exact(&mut tmp)?;
NonNegativeAmount::from_nonnegative_i64_le_bytes(tmp)
}
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
let script_pubkey = Script::read(&mut reader)?;
Ok(TxOut {
value,
script_pubkey,
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.value.to_i64_le_bytes())?;
self.script_pubkey.write(&mut writer)
}
pub fn recipient_address(&self) -> Option<TransparentAddress> {
self.script_pubkey.address()
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::select;
use crate::{legacy::Script, transaction::components::amount::testing::arb_nonnegative_amount};
use super::{Authorized, Bundle, OutPoint, TxIn, TxOut};
pub const VALID_OPCODES: [u8; 8] = [
0x00, 0x51, 0x52, 0x53, 0xac, 0x63, 0x65, 0x6a, ];
prop_compose! {
pub fn arb_outpoint()(hash in prop::array::uniform32(0u8..), n in 0..100u32) -> OutPoint {
OutPoint::new(hash, n)
}
}
prop_compose! {
pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
Script(v)
}
}
prop_compose! {
pub fn arb_txin()(
prevout in arb_outpoint(),
script_sig in arb_script(),
sequence in any::<u32>()
) -> TxIn<Authorized> {
TxIn { prevout, script_sig, sequence }
}
}
prop_compose! {
pub fn arb_txout()(value in arb_nonnegative_amount(), script_pubkey in arb_script()) -> TxOut {
TxOut { value, script_pubkey }
}
}
prop_compose! {
pub fn arb_bundle()(
vin in vec(arb_txin(), 0..10),
vout in vec(arb_txout(), 0..10),
) -> Option<Bundle<Authorized>> {
if vin.is_empty() && vout.is_empty() {
None
} else {
Some(Bundle { vin, vout, authorization: Authorized })
}
}
}
}