1mod address;
4mod keys;
5mod opcodes;
6mod script;
7mod serialize;
8mod utxo;
9
10use std::{collections::HashMap, fmt, iter, ops::AddAssign};
11
12use zcash_script::{opcode::Evaluable as _, pattern::push_num};
13use zcash_transparent::{address::TransparentAddress, bundle::TxOut};
14
15use crate::{
16 amount::{Amount, NonNegative},
17 block,
18 parameters::Network,
19 serialization::ZcashSerialize,
20 transaction,
21 transparent::serialize::GENESIS_COINBASE_SCRIPT_SIG,
22};
23
24pub use address::Address;
25pub use script::Script;
26pub use utxo::{
27 new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
28 CoinbaseSpendRestriction, OrderedUtxo, Utxo,
29};
30
31#[cfg(any(test, feature = "proptest-impl"))]
32pub use utxo::{
33 new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
34};
35
36#[cfg(any(test, feature = "proptest-impl"))]
37mod arbitrary;
38
39#[cfg(test)]
40mod tests;
41
42#[cfg(any(test, feature = "proptest-impl"))]
43use proptest_derive::Arbitrary;
44
45pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
55
56pub const ONE_THIRD_DUST_THRESHOLD_RATE: u32 = 100;
60
61#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
65#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
66#[cfg_attr(
67 any(test, feature = "proptest-impl", feature = "elasticsearch"),
68 derive(Serialize)
69)]
70pub struct OutPoint {
71 #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))]
78 pub hash: transaction::Hash,
79
80 pub index: u32,
84}
85
86impl OutPoint {
87 pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
93 OutPoint {
94 hash,
95 index: index
96 .try_into()
97 .expect("valid in-memory output indexes fit in a u32"),
98 }
99 }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
104#[cfg_attr(
105 any(test, feature = "proptest-impl", feature = "elasticsearch"),
106 derive(Serialize)
107)]
108pub enum Input {
109 PrevOut {
111 outpoint: OutPoint,
113 unlock_script: Script,
115 sequence: u32,
117 },
118 Coinbase {
120 height: block::Height,
122 data: Vec<u8>,
125 sequence: u32,
127 },
128}
129
130impl fmt::Display for Input {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 Input::PrevOut {
134 outpoint,
135 unlock_script,
136 ..
137 } => {
138 let mut fmter = f.debug_struct("transparent::Input::PrevOut");
139
140 fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
141 fmter.field("outpoint", outpoint);
142
143 fmter.finish()
144 }
145 Input::Coinbase { height, data, .. } => {
146 let mut fmter = f.debug_struct("transparent::Input::Coinbase");
147
148 fmter.field("height", height);
149 fmter.field("data_len", &data.len());
150
151 fmter.finish()
152 }
153 }
154 }
155}
156
157impl Input {
158 pub fn miner_data(&self) -> Option<&Vec<u8>> {
160 match self {
161 Input::Coinbase { data, .. } => Some(data),
162 _ => None,
163 }
164 }
165
166 pub fn coinbase_script(&self) -> Option<Vec<u8>> {
170 match self {
171 Input::PrevOut { .. } => None,
172 Input::Coinbase { height, data, .. } => {
173 if height.is_min() {
174 (data.as_slice() == GENESIS_COINBASE_SCRIPT_SIG)
175 .then_some(GENESIS_COINBASE_SCRIPT_SIG.to_vec())
176 } else {
177 let mut script = push_num(height.into()).to_bytes();
178 script.extend_from_slice(data);
179
180 Some(script)
181 }
182 }
183 }
184 }
185
186 pub fn sequence(&self) -> u32 {
188 match self {
189 Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
190 }
191 }
192
193 #[cfg(any(test, feature = "proptest-impl"))]
197 pub fn set_sequence(&mut self, new_sequence: u32) {
198 match self {
199 Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
200 *sequence = new_sequence
201 }
202 }
203 }
204
205 pub fn outpoint(&self) -> Option<OutPoint> {
208 if let Input::PrevOut { outpoint, .. } = self {
209 Some(*outpoint)
210 } else {
211 None
212 }
213 }
214
215 #[cfg(any(test, feature = "proptest-impl"))]
223 pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
224 if let Input::PrevOut {
225 ref mut outpoint, ..
226 } = self
227 {
228 *outpoint = new_outpoint;
229 } else {
230 unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
231 }
232 }
233
234 pub(crate) fn value_from_outputs(
241 &self,
242 outputs: &HashMap<OutPoint, Output>,
243 ) -> Amount<NonNegative> {
244 match self {
245 Input::PrevOut { outpoint, .. } => {
246 outputs
247 .get(outpoint)
248 .unwrap_or_else(|| {
249 panic!(
250 "provided Outputs (length {:?}) don't have spent {:?}",
251 outputs.len(),
252 outpoint
253 )
254 })
255 .value
256 }
257 Input::Coinbase { .. } => Amount::zero(),
258 }
259 }
260
261 pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
270 if let Some(outpoint) = self.outpoint() {
271 let output = utxos
273 .get(&outpoint)
274 .expect("provided Utxos don't have spent OutPoint")
275 .output
276 .clone();
277 self.value_from_outputs(&iter::once((outpoint, output)).collect())
278 } else {
279 self.value_from_outputs(&HashMap::new())
281 }
282 }
283
284 pub fn value_from_ordered_utxos(
293 &self,
294 ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
295 ) -> Amount<NonNegative> {
296 if let Some(outpoint) = self.outpoint() {
297 let output = ordered_utxos
299 .get(&outpoint)
300 .expect("provided Utxos don't have spent OutPoint")
301 .utxo
302 .output
303 .clone();
304 self.value_from_outputs(&iter::once((outpoint, output)).collect())
305 } else {
306 self.value_from_outputs(&HashMap::new())
308 }
309 }
310}
311
312#[derive(Clone, Debug, Eq, PartialEq, Hash)]
325#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
326#[cfg_attr(
327 any(test, feature = "proptest-impl", feature = "elasticsearch"),
328 derive(Serialize)
329)]
330pub struct Output {
331 pub value: Amount<NonNegative>,
334
335 pub lock_script: Script,
337}
338
339impl Output {
340 pub fn new(amount: Amount<NonNegative>, lock_script: Script) -> Output {
342 Output {
343 value: amount,
344 lock_script,
345 }
346 }
347
348 pub fn value(&self) -> Amount<NonNegative> {
351 self.value
352 }
353
354 pub fn address(&self, net: &Network) -> Option<Address> {
358 match TxOut::try_from(self).ok()?.recipient_address()? {
359 TransparentAddress::PublicKeyHash(pkh) => {
360 Some(Address::from_pub_key_hash(net.t_addr_kind(), pkh))
361 }
362 TransparentAddress::ScriptHash(sh) => {
363 Some(Address::from_script_hash(net.t_addr_kind(), sh))
364 }
365 }
366 }
367
368 pub fn is_dust(&self) -> bool {
370 let output_size: u32 = self
371 .zcash_serialized_size()
372 .try_into()
373 .expect("output size should fit in u32");
374
375 let threshold = 3 * (ONE_THIRD_DUST_THRESHOLD_RATE * (output_size + 148) / 1000);
377
378 self.value.zatoshis() < threshold as i64
380 }
381}
382
383#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
385pub struct OutputIndex(u32);
386
387impl OutputIndex {
388 pub const fn from_index(output_index: u32) -> OutputIndex {
392 OutputIndex(output_index)
393 }
394
395 pub const fn index(&self) -> u32 {
397 self.0
398 }
399
400 #[allow(dead_code)]
402 pub fn from_usize(output_index: usize) -> OutputIndex {
403 OutputIndex(
404 output_index
405 .try_into()
406 .expect("the maximum valid index fits in the inner type"),
407 )
408 }
409
410 #[allow(dead_code)]
412 pub fn as_usize(&self) -> usize {
413 self.0
414 .try_into()
415 .expect("the maximum valid index fits in usize")
416 }
417
418 #[allow(dead_code)]
420 pub fn from_u64(output_index: u64) -> OutputIndex {
421 OutputIndex(
422 output_index
423 .try_into()
424 .expect("the maximum u64 index fits in the inner type"),
425 )
426 }
427
428 #[allow(dead_code)]
430 pub fn as_u64(&self) -> u64 {
431 self.0.into()
432 }
433}
434
435impl AddAssign<u32> for OutputIndex {
436 fn add_assign(&mut self, rhs: u32) {
437 self.0 += rhs
438 }
439}