use std::io::{Read, Write};
use coins_core::{
hashes::MarkedDigestOutput,
ser::{ByteFormat, SerError, SerResult},
types::tx::{Input, TxoIdentifier},
};
use crate::{hashes::TXID, types::script::ScriptSig};
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Outpoint<M>
where
M: MarkedDigestOutput,
{
pub txid: M,
pub idx: u32,
}
impl<M> TxoIdentifier for Outpoint<M> where M: MarkedDigestOutput {}
impl<M> Outpoint<M>
where
M: MarkedDigestOutput,
{
pub fn new(txid: M, idx: u32) -> Self {
Self { txid, idx }
}
pub fn null() -> Self {
Outpoint {
txid: M::default(),
idx: 0xffff_ffff,
}
}
pub fn txid_be_hex(&self) -> String {
self.txid.reversed().serialize_hex()
}
pub fn from_explorer_format(txid_be: M, idx: u32) -> Self {
Self {
txid: txid_be.reversed(),
idx,
}
}
}
impl<M> Default for Outpoint<M>
where
M: MarkedDigestOutput,
{
fn default() -> Self {
Outpoint::null()
}
}
impl<M> ByteFormat for Outpoint<M>
where
M: MarkedDigestOutput + ByteFormat,
{
type Error = SerError;
fn serialized_length(&self) -> usize {
36
}
fn read_from<T>(reader: &mut T) -> SerResult<Self>
where
T: Read,
Self: std::marker::Sized,
{
Ok(Outpoint {
txid: M::read_from(reader).map_err(|e| SerError::ComponentError(format!("{}", e)))?,
idx: coins_core::ser::read_u32_le(reader)?,
})
}
fn write_to<T>(&self, writer: &mut T) -> SerResult<usize>
where
T: Write,
{
let mut len = self
.txid
.write_to(writer)
.map_err(|e| SerError::ComponentError(format!("{}", e)))?;
len += coins_core::ser::write_u32_le(writer, self.idx)?;
Ok(len)
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, Default)]
pub struct TxInput<M>
where
M: MarkedDigestOutput,
{
pub outpoint: Outpoint<M>,
pub script_sig: ScriptSig,
pub sequence: u32,
}
impl<M> Input for TxInput<M>
where
M: MarkedDigestOutput,
{
type TxoIdentifier = Outpoint<M>;
}
impl<M> TxInput<M>
where
M: MarkedDigestOutput,
{
pub fn new<T>(outpoint: Outpoint<M>, script_sig: T, sequence: u32) -> Self
where
T: Into<ScriptSig>,
{
TxInput {
outpoint,
script_sig: script_sig.into(),
sequence,
}
}
pub fn unsigned(&self) -> TxInput<M> {
Self::new(self.outpoint, vec![], self.sequence)
}
}
impl<M> ByteFormat for TxInput<M>
where
M: MarkedDigestOutput + ByteFormat,
{
type Error = SerError;
fn serialized_length(&self) -> usize {
let mut len = self.outpoint.serialized_length();
len += self.script_sig.serialized_length();
len += 4; len
}
fn read_from<T>(reader: &mut T) -> SerResult<Self>
where
T: Read,
Self: std::marker::Sized,
{
Ok(TxInput {
outpoint: Outpoint::read_from(reader)?,
script_sig: ScriptSig::read_from(reader)?,
sequence: coins_core::ser::read_u32_le(reader)?,
})
}
fn write_to<T>(&self, writer: &mut T) -> SerResult<usize>
where
T: Write,
{
let mut len = self.outpoint.write_to(writer)?;
len += self.script_sig.write_to(writer)?;
len += coins_core::ser::write_u32_le(writer, self.sequence)?;
Ok(len)
}
}
pub type BitcoinOutpoint = Outpoint<TXID>;
pub type BitcoinTxIn = TxInput<TXID>;
pub type Vin = Vec<BitcoinTxIn>;
#[cfg(test)]
mod test {
use super::*;
use coins_core::ser::ByteFormat;
static NULL_OUTPOINT: &str =
"0000000000000000000000000000000000000000000000000000000000000000ffffffff";
#[test]
fn it_serializes_and_derializes_outpoints() {
let cases = [
(
Outpoint::<TXID> {
txid: TXID::default(),
idx: 0,
},
(0..36).map(|_| "00").collect::<String>(),
),
(Outpoint::<TXID>::null(), NULL_OUTPOINT.to_string()),
];
for case in cases.iter() {
assert_eq!(case.0.serialized_length(), case.1.len() / 2);
assert_eq!(case.0.serialize_hex(), case.1);
assert_eq!(Outpoint::<TXID>::deserialize_hex(&case.1).unwrap(), case.0);
}
}
#[test]
fn it_serializes_and_derializes_inputs() {
let cases = [
(
BitcoinTxIn {
outpoint: Outpoint::null(),
script_sig: ScriptSig::null(),
sequence: 0x1234abcd,
},
format!("{}{}{}", NULL_OUTPOINT, "00", "cdab3412"),
),
(
BitcoinTxIn::new(Outpoint::null(), vec![], 0x11223344),
format!("{}{}{}", NULL_OUTPOINT, "00", "44332211"),
),
];
for case in cases.iter() {
assert_eq!(case.0.serialized_length(), case.1.len() / 2);
assert_eq!(case.0.serialize_hex(), case.1);
assert_eq!(BitcoinTxIn::deserialize_hex(&case.1).unwrap(), case.0);
}
}
}