use std::collections::HashMap;
use zebra_chain::{
amount,
transparent::{self, utxos_from_ordered_utxos, CoinbaseSpendRestriction::*},
};
use crate::{
constants::MIN_TRANSPARENT_COINBASE_MATURITY,
service::{finalized_state::ZebraDb, non_finalized_state::SpendingTransactionId},
SemanticallyVerifiedBlock,
ValidateContextError::{
self, DuplicateTransparentSpend, EarlyTransparentSpend, ImmatureTransparentCoinbaseSpend,
MissingTransparentOutput, UnshieldedTransparentCoinbaseSpend,
},
};
pub fn transparent_spend(
semantically_verified: &SemanticallyVerifiedBlock,
non_finalized_chain_unspent_utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
non_finalized_chain_spent_utxos: &HashMap<transparent::OutPoint, SpendingTransactionId>,
finalized_state: &ZebraDb,
) -> Result<HashMap<transparent::OutPoint, transparent::OrderedUtxo>, ValidateContextError> {
let mut block_spends = HashMap::new();
for (spend_tx_index_in_block, transaction) in
semantically_verified.block.transactions.iter().enumerate()
{
let spends = transaction
.inputs()
.iter()
.filter_map(transparent::Input::outpoint);
for spend in spends {
let utxo = transparent_spend_chain_order(
spend,
spend_tx_index_in_block,
&semantically_verified.new_outputs,
non_finalized_chain_unspent_utxos,
non_finalized_chain_spent_utxos,
finalized_state,
)?;
let spend_restriction = transaction.coinbase_spend_restriction(
&finalized_state.network(),
semantically_verified.height,
);
transparent_coinbase_spend(spend, spend_restriction, utxo.as_ref())?;
if block_spends.insert(spend, utxo).is_some() {
return Err(DuplicateTransparentSpend {
outpoint: spend,
location: "the same block",
});
}
}
}
remaining_transaction_value(semantically_verified, &block_spends)?;
Ok(block_spends)
}
fn transparent_spend_chain_order(
spend: transparent::OutPoint,
spend_tx_index_in_block: usize,
block_new_outputs: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
non_finalized_chain_unspent_utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
non_finalized_chain_spent_utxos: &HashMap<transparent::OutPoint, SpendingTransactionId>,
finalized_state: &ZebraDb,
) -> Result<transparent::OrderedUtxo, ValidateContextError> {
if let Some(output) = block_new_outputs.get(&spend) {
if output.tx_index_in_block >= spend_tx_index_in_block {
return Err(EarlyTransparentSpend { outpoint: spend });
} else {
return Ok(output.clone());
}
}
if non_finalized_chain_spent_utxos.contains_key(&spend) {
return Err(DuplicateTransparentSpend {
outpoint: spend,
location: "the non-finalized chain",
});
}
non_finalized_chain_unspent_utxos
.get(&spend)
.cloned()
.or_else(|| finalized_state.utxo(&spend))
.ok_or(MissingTransparentOutput {
outpoint: spend,
location: "the non-finalized and finalized chain",
})
}
pub fn transparent_coinbase_spend(
outpoint: transparent::OutPoint,
spend_restriction: transparent::CoinbaseSpendRestriction,
utxo: &transparent::Utxo,
) -> Result<(), ValidateContextError> {
if !utxo.from_coinbase {
return Ok(());
}
match spend_restriction {
CheckCoinbaseMaturity { spend_height } => {
let min_spend_height = utxo.height + MIN_TRANSPARENT_COINBASE_MATURITY.into();
let min_spend_height =
min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX");
if spend_height >= min_spend_height {
Ok(())
} else {
Err(ImmatureTransparentCoinbaseSpend {
outpoint,
spend_height,
min_spend_height,
created_height: utxo.height,
})
}
}
DisallowCoinbaseSpend => Err(UnshieldedTransparentCoinbaseSpend { outpoint }),
}
}
pub fn remaining_transaction_value(
semantically_verified: &SemanticallyVerifiedBlock,
utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
) -> Result<(), ValidateContextError> {
for (tx_index_in_block, transaction) in
semantically_verified.block.transactions.iter().enumerate()
{
if transaction.is_coinbase() {
continue;
}
let value_balance = transaction.value_balance(&utxos_from_ordered_utxos(utxos.clone()));
match value_balance {
Ok(vb) => match vb.remaining_transaction_value() {
Ok(_) => Ok(()),
Err(amount_error @ amount::Error::Constraint { .. })
if amount_error.invalid_value() < 0 =>
{
Err(ValidateContextError::NegativeRemainingTransactionValue {
amount_error,
height: semantically_verified.height,
tx_index_in_block,
transaction_hash: semantically_verified.transaction_hashes
[tx_index_in_block],
})
}
Err(amount_error) => {
Err(ValidateContextError::CalculateRemainingTransactionValue {
amount_error,
height: semantically_verified.height,
tx_index_in_block,
transaction_hash: semantically_verified.transaction_hashes
[tx_index_in_block],
})
}
},
Err(value_balance_error) => {
Err(ValidateContextError::CalculateTransactionValueBalances {
value_balance_error,
height: semantically_verified.height,
tx_index_in_block,
transaction_hash: semantically_verified.transaction_hashes[tx_index_in_block],
})
}
}?
}
Ok(())
}