Correct coinbase check (#898)

* chain: add Transaction::is_coinbase()

This matches the check in zcashd/src/primitives/transaction.h:682
(CTransaction::IsCoinBase).

* chain: correct Block::is_coinbase_first

This matches zcashd/src/main.cpp:3968-3974 in CheckBlock.

Previously, the check allowed the first transaction to have multiple coinbase inputs.

* chain: return slices from Transaction::inputs()/outputs()

They're slices internally so we might as well just expose them that way.
This commit is contained in:
Henry de Valence 2020-08-13 14:04:43 -07:00 committed by GitHub
parent e73f976194
commit 07917421cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 27 deletions

View File

@ -57,7 +57,7 @@ impl Block {
use crate::transaction::TransparentInput; use crate::transaction::TransparentInput;
self.transactions self.transactions
.get(0) .get(0)
.and_then(|tx| tx.inputs().next()) .and_then(|tx| tx.inputs().get(0))
.and_then(|input| match input { .and_then(|input| match input {
TransparentInput::Coinbase { ref height, .. } => Some(*height), TransparentInput::Coinbase { ref height, .. } => Some(*height),
_ => None, _ => None,
@ -69,24 +69,22 @@ impl Block {
/// ///
/// "The first (and only the first) transaction in a block is a coinbase /// "The first (and only the first) transaction in a block is a coinbase
/// transaction, which collects and spends any miner subsidy and transaction /// transaction, which collects and spends any miner subsidy and transaction
/// fees paid by transactions included in this block."[S 3.10][3.10] /// fees paid by transactions included in this block." 3.10][3.10]
/// ///
/// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions /// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions
pub fn is_coinbase_first(&self) -> Result<(), Error> { pub fn is_coinbase_first(&self) -> Result<(), Error> {
if self.coinbase_height().is_some() { let first = self
// No coinbase inputs in additional transactions allowed .transactions
if self .get(0)
.transactions .ok_or_else(|| "block has no transactions")?;
.iter() let mut rest = self.transactions.iter().skip(1);
.skip(1) if !first.is_coinbase() {
.any(|tx| tx.contains_coinbase_input()) Err("first transaction must be coinbase")?;
{
Err("coinbase input found in additional transaction")?
}
Ok(())
} else {
Err("no coinbase transaction in block")?
} }
if rest.any(|tx| tx.contains_coinbase_input()) {
Err("coinbase input found in non-coinbase transaction")?;
}
Ok(())
} }
/// Get the hash for the current block /// Get the hash for the current block

View File

@ -91,23 +91,23 @@ pub enum Transaction {
} }
impl Transaction { impl Transaction {
/// Iterate over the transparent inputs of this transaction, if any. /// Access the transparent inputs of this transaction, regardless of version.
pub fn inputs(&self) -> impl Iterator<Item = &TransparentInput> { pub fn inputs(&self) -> &[TransparentInput] {
match self { match self {
Transaction::V1 { ref inputs, .. } => inputs.iter(), Transaction::V1 { ref inputs, .. } => inputs,
Transaction::V2 { ref inputs, .. } => inputs.iter(), Transaction::V2 { ref inputs, .. } => inputs,
Transaction::V3 { ref inputs, .. } => inputs.iter(), Transaction::V3 { ref inputs, .. } => inputs,
Transaction::V4 { ref inputs, .. } => inputs.iter(), Transaction::V4 { ref inputs, .. } => inputs,
} }
} }
/// Iterate over the transparent outputs of this transaction, if any. /// Access the transparent outputs of this transaction, regardless of version.
pub fn outputs(&self) -> impl Iterator<Item = &TransparentOutput> { pub fn outputs(&self) -> &[TransparentOutput] {
match self { match self {
Transaction::V1 { ref outputs, .. } => outputs.iter(), Transaction::V1 { ref outputs, .. } => outputs,
Transaction::V2 { ref outputs, .. } => outputs.iter(), Transaction::V2 { ref outputs, .. } => outputs,
Transaction::V3 { ref outputs, .. } => outputs.iter(), Transaction::V3 { ref outputs, .. } => outputs,
Transaction::V4 { ref outputs, .. } => outputs.iter(), Transaction::V4 { ref outputs, .. } => outputs,
} }
} }
@ -134,6 +134,16 @@ impl Transaction {
/// Returns `true` if transaction contains any coinbase inputs. /// Returns `true` if transaction contains any coinbase inputs.
pub fn contains_coinbase_input(&self) -> bool { pub fn contains_coinbase_input(&self) -> bool {
self.inputs() self.inputs()
.iter()
.any(|input| matches!(input, TransparentInput::Coinbase { .. })) .any(|input| matches!(input, TransparentInput::Coinbase { .. }))
} }
/// Returns `true` if this transaction is a coinbase transaction.
pub fn is_coinbase(&self) -> bool {
self.inputs().len() == 1
&& matches!(
self.inputs().get(0),
Some(TransparentInput::Coinbase { .. })
)
}
} }