use crate::core::core::hash::{Hash, Hashed};
use crate::core::core::pmmr::{self, ReadonlyPMMR};
use crate::core::core::{Block, BlockHeader, Input, Output, Transaction};
use crate::core::global;
use crate::core::ser::PMMRIndexHashable;
use crate::error::{Error, ErrorKind};
use crate::store::Batch;
use grin_store::pmmr::PMMRBackend;
pub struct UTXOView<'a> {
output_pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
batch: &'a Batch<'a>,
}
impl<'a> UTXOView<'a> {
pub fn new(
output_pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
batch: &'a Batch<'_>,
) -> UTXOView<'a> {
UTXOView {
output_pmmr,
header_pmmr,
batch,
}
}
pub fn validate_block(&self, block: &Block) -> Result<(), Error> {
for output in block.outputs() {
self.validate_output(output)?;
}
for input in block.inputs() {
self.validate_input(input)?;
}
Ok(())
}
pub fn validate_tx(&self, tx: &Transaction) -> Result<(), Error> {
for output in tx.outputs() {
self.validate_output(output)?;
}
for input in tx.inputs() {
self.validate_input(input)?;
}
Ok(())
}
fn validate_input(&self, input: &Input) -> Result<(), Error> {
if let Ok(pos) = self.batch.get_output_pos(&input.commitment()) {
if let Some(hash) = self.output_pmmr.get_hash(pos) {
if hash == input.hash_with_index(pos - 1) {
return Ok(());
}
}
}
Err(ErrorKind::AlreadySpent(input.commitment()).into())
}
fn validate_output(&self, output: &Output) -> Result<(), Error> {
if let Ok(pos) = self.batch.get_output_pos(&output.commitment()) {
if let Some(out_mmr) = self.output_pmmr.get_data(pos) {
if out_mmr.commitment() == output.commitment() {
return Err(ErrorKind::DuplicateCommitment(output.commitment()).into());
}
}
}
Ok(())
}
pub fn verify_coinbase_maturity(&self, inputs: &Vec<Input>, height: u64) -> Result<(), Error> {
let pos = inputs
.iter()
.filter(|x| x.is_coinbase())
.filter_map(|x| self.batch.get_output_pos(&x.commitment()).ok())
.max()
.unwrap_or(0);
if pos > 0 {
if height < global::coinbase_maturity() {
return Err(ErrorKind::ImmatureCoinbase.into());
}
let cutoff_height = height.checked_sub(global::coinbase_maturity()).unwrap_or(0);
let cutoff_header = self.get_header_by_height(cutoff_height)?;
let cutoff_pos = cutoff_header.output_mmr_size;
if pos > cutoff_pos {
return Err(ErrorKind::ImmatureCoinbase.into());
}
}
Ok(())
}
fn get_header_hash(&self, pos: u64) -> Option<Hash> {
self.header_pmmr.get_data(pos).map(|x| x.hash())
}
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
let pos = pmmr::insertion_to_pmmr_index(height + 1);
if let Some(hash) = self.get_header_hash(pos) {
let header = self.batch.get_block_header(&hash)?;
Ok(header)
} else {
Err(ErrorKind::Other(format!("get header by height")).into())
}
}
}