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;
self.transactions
.get(0)
.and_then(|tx| tx.inputs().next())
.and_then(|tx| tx.inputs().get(0))
.and_then(|input| match input {
TransparentInput::Coinbase { ref height, .. } => Some(*height),
_ => None,
@ -69,24 +69,22 @@ impl Block {
///
/// "The first (and only the first) transaction in a block is a coinbase
/// 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
pub fn is_coinbase_first(&self) -> Result<(), Error> {
if self.coinbase_height().is_some() {
// No coinbase inputs in additional transactions allowed
if self
.transactions
.iter()
.skip(1)
.any(|tx| tx.contains_coinbase_input())
{
Err("coinbase input found in additional transaction")?
}
Ok(())
} else {
Err("no coinbase transaction in block")?
let first = self
.transactions
.get(0)
.ok_or_else(|| "block has no transactions")?;
let mut rest = self.transactions.iter().skip(1);
if !first.is_coinbase() {
Err("first transaction must be coinbase")?;
}
if rest.any(|tx| tx.contains_coinbase_input()) {
Err("coinbase input found in non-coinbase transaction")?;
}
Ok(())
}
/// Get the hash for the current block

View File

@ -91,23 +91,23 @@ pub enum Transaction {
}
impl Transaction {
/// Iterate over the transparent inputs of this transaction, if any.
pub fn inputs(&self) -> impl Iterator<Item = &TransparentInput> {
/// Access the transparent inputs of this transaction, regardless of version.
pub fn inputs(&self) -> &[TransparentInput] {
match self {
Transaction::V1 { ref inputs, .. } => inputs.iter(),
Transaction::V2 { ref inputs, .. } => inputs.iter(),
Transaction::V3 { ref inputs, .. } => inputs.iter(),
Transaction::V4 { ref inputs, .. } => inputs.iter(),
Transaction::V1 { ref inputs, .. } => inputs,
Transaction::V2 { ref inputs, .. } => inputs,
Transaction::V3 { ref inputs, .. } => inputs,
Transaction::V4 { ref inputs, .. } => inputs,
}
}
/// Iterate over the transparent outputs of this transaction, if any.
pub fn outputs(&self) -> impl Iterator<Item = &TransparentOutput> {
/// Access the transparent outputs of this transaction, regardless of version.
pub fn outputs(&self) -> &[TransparentOutput] {
match self {
Transaction::V1 { ref outputs, .. } => outputs.iter(),
Transaction::V2 { ref outputs, .. } => outputs.iter(),
Transaction::V3 { ref outputs, .. } => outputs.iter(),
Transaction::V4 { ref outputs, .. } => outputs.iter(),
Transaction::V1 { ref outputs, .. } => outputs,
Transaction::V2 { ref outputs, .. } => outputs,
Transaction::V3 { ref outputs, .. } => outputs,
Transaction::V4 { ref outputs, .. } => outputs,
}
}
@ -134,6 +134,16 @@ impl Transaction {
/// Returns `true` if transaction contains any coinbase inputs.
pub fn contains_coinbase_input(&self) -> bool {
self.inputs()
.iter()
.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 { .. })
)
}
}