mod address;
mod keys;
mod opcodes;
mod script;
mod serialize;
mod utxo;
use std::{collections::HashMap, fmt, iter, ops::AddAssign};
use zcash_transparent::{address::TransparentAddress, bundle::TxOut};
use crate::{
amount::{Amount, NonNegative},
block,
parameters::Network,
serialization::ZcashSerialize,
transaction,
};
pub use address::Address;
pub use script::Script;
pub use serialize::{GENESIS_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN};
pub use utxo::{
new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
CoinbaseSpendRestriction, OrderedUtxo, Utxo,
};
#[cfg(any(test, feature = "proptest-impl"))]
pub use utxo::{
new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
};
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
#[cfg(test)]
mod tests;
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
pub const EXTRA_ZEBRA_COINBASE_DATA: &str = "z\u{1F993}";
pub const ONE_THIRD_DUST_THRESHOLD_RATE: u32 = 100;
#[derive(Clone, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl", feature = "elasticsearch"),
derive(Serialize)
)]
pub struct CoinbaseData(
pub(super) Vec<u8>,
);
#[cfg(any(test, feature = "proptest-impl"))]
impl CoinbaseData {
pub fn new(data: Vec<u8>) -> CoinbaseData {
CoinbaseData(data)
}
}
impl AsRef<[u8]> for CoinbaseData {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl std::fmt::Debug for CoinbaseData {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let escaped = String::from_utf8(
self.0
.iter()
.cloned()
.flat_map(std::ascii::escape_default)
.collect(),
)
.expect("ascii::escape_default produces utf8");
f.debug_tuple("CoinbaseData").field(&escaped).finish()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
#[cfg_attr(
any(test, feature = "proptest-impl", feature = "elasticsearch"),
derive(Serialize)
)]
pub struct OutPoint {
#[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))]
pub hash: transaction::Hash,
pub index: u32,
}
impl OutPoint {
pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
OutPoint {
hash,
index: index
.try_into()
.expect("valid in-memory output indexes fit in a u32"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "proptest-impl", feature = "elasticsearch"),
derive(Serialize)
)]
pub enum Input {
PrevOut {
outpoint: OutPoint,
unlock_script: Script,
sequence: u32,
},
Coinbase {
height: block::Height,
data: CoinbaseData,
sequence: u32,
},
}
impl fmt::Display for Input {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Input::PrevOut {
outpoint,
unlock_script,
..
} => {
let mut fmter = f.debug_struct("transparent::Input::PrevOut");
fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
fmter.field("outpoint", outpoint);
fmter.finish()
}
Input::Coinbase { height, data, .. } => {
let mut fmter = f.debug_struct("transparent::Input::Coinbase");
fmter.field("height", height);
fmter.field("data_len", &data.0.len());
fmter.finish()
}
}
}
}
impl Input {
pub fn new_coinbase(height: block::Height, data: Vec<u8>, sequence: Option<u32>) -> Input {
let data = if data.is_empty() { vec![0] } else { data };
let data_limit = MAX_COINBASE_DATA_LEN - height.coinbase_zcash_serialized_size();
assert!(
data.len() <= data_limit,
"miner data has {} bytes, which exceeds the limit of {data_limit} bytes",
data.len(),
);
Input::Coinbase {
height,
data: CoinbaseData(data),
sequence: sequence.unwrap_or(0),
}
}
pub fn extra_coinbase_data(&self) -> Option<&CoinbaseData> {
match self {
Input::PrevOut { .. } => None,
Input::Coinbase { data, .. } => Some(data),
}
}
pub fn coinbase_script(&self) -> Option<Vec<u8>> {
match self {
Input::PrevOut { .. } => None,
Input::Coinbase { height, data, .. } => {
let mut height_and_data = Vec::new();
serialize::write_coinbase_height(*height, data, &mut height_and_data).ok()?;
height_and_data.extend(&data.0);
Some(height_and_data)
}
}
}
pub fn sequence(&self) -> u32 {
match self {
Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
}
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn set_sequence(&mut self, new_sequence: u32) {
match self {
Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
*sequence = new_sequence
}
}
}
pub fn outpoint(&self) -> Option<OutPoint> {
if let Input::PrevOut { outpoint, .. } = self {
Some(*outpoint)
} else {
None
}
}
#[cfg(any(test, feature = "proptest-impl"))]
pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
if let Input::PrevOut {
ref mut outpoint, ..
} = self
{
*outpoint = new_outpoint;
} else {
unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
}
}
pub(crate) fn value_from_outputs(
&self,
outputs: &HashMap<OutPoint, Output>,
) -> Amount<NonNegative> {
match self {
Input::PrevOut { outpoint, .. } => {
outputs
.get(outpoint)
.unwrap_or_else(|| {
panic!(
"provided Outputs (length {:?}) don't have spent {:?}",
outputs.len(),
outpoint
)
})
.value
}
Input::Coinbase { .. } => Amount::zero(),
}
}
pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
if let Some(outpoint) = self.outpoint() {
let output = utxos
.get(&outpoint)
.expect("provided Utxos don't have spent OutPoint")
.output
.clone();
self.value_from_outputs(&iter::once((outpoint, output)).collect())
} else {
self.value_from_outputs(&HashMap::new())
}
}
pub fn value_from_ordered_utxos(
&self,
ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
) -> Amount<NonNegative> {
if let Some(outpoint) = self.outpoint() {
let output = ordered_utxos
.get(&outpoint)
.expect("provided Utxos don't have spent OutPoint")
.utxo
.output
.clone();
self.value_from_outputs(&iter::once((outpoint, output)).collect())
} else {
self.value_from_outputs(&HashMap::new())
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
#[cfg_attr(
any(test, feature = "proptest-impl", feature = "elasticsearch"),
derive(Serialize)
)]
pub struct Output {
pub value: Amount<NonNegative>,
pub lock_script: Script,
}
impl Output {
pub fn new(amount: Amount<NonNegative>, lock_script: Script) -> Output {
Output {
value: amount,
lock_script,
}
}
pub fn value(&self) -> Amount<NonNegative> {
self.value
}
pub fn address(&self, net: &Network) -> Option<Address> {
match TxOut::try_from(self).ok()?.recipient_address()? {
TransparentAddress::PublicKeyHash(pkh) => {
Some(Address::from_pub_key_hash(net.t_addr_kind(), pkh))
}
TransparentAddress::ScriptHash(sh) => {
Some(Address::from_script_hash(net.t_addr_kind(), sh))
}
}
}
pub fn is_dust(&self) -> bool {
let output_size: u32 = self
.zcash_serialized_size()
.try_into()
.expect("output size should fit in u32");
let threshold = 3 * (ONE_THIRD_DUST_THRESHOLD_RATE * (output_size + 148) / 1000);
self.value.zatoshis() < threshold as i64
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct OutputIndex(u32);
impl OutputIndex {
pub const fn from_index(output_index: u32) -> OutputIndex {
OutputIndex(output_index)
}
pub const fn index(&self) -> u32 {
self.0
}
#[allow(dead_code)]
pub fn from_usize(output_index: usize) -> OutputIndex {
OutputIndex(
output_index
.try_into()
.expect("the maximum valid index fits in the inner type"),
)
}
#[allow(dead_code)]
pub fn as_usize(&self) -> usize {
self.0
.try_into()
.expect("the maximum valid index fits in usize")
}
#[allow(dead_code)]
pub fn from_u64(output_index: u64) -> OutputIndex {
OutputIndex(
output_index
.try_into()
.expect("the maximum u64 index fits in the inner type"),
)
}
#[allow(dead_code)]
pub fn as_u64(&self) -> u64 {
self.0.into()
}
}
impl AddAssign<u32> for OutputIndex {
fn add_assign(&mut self, rhs: u32) {
self.0 += rhs
}
}