use crate::core::core::hash::{Hash, Hashed};
use crate::core::core::pmmr::{self, ReadablePMMR, ReadonlyPMMR};
use crate::core::core::{Block, BlockHeader, Inputs, Output, OutputIdentifier, Transaction};
use crate::core::global;
use crate::error::Error;
use crate::store::Batch;
use crate::types::CommitPos;
use crate::util::secp::pedersen::{Commitment, RangeProof};
use grin_store::pmmr::PMMRBackend;
pub struct UTXOView<'a> {
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
output_pmmr: ReadonlyPMMR<'a, OutputIdentifier, PMMRBackend<OutputIdentifier>>,
rproof_pmmr: ReadonlyPMMR<'a, RangeProof, PMMRBackend<RangeProof>>,
}
impl<'a> UTXOView<'a> {
pub fn new(
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
output_pmmr: ReadonlyPMMR<'a, OutputIdentifier, PMMRBackend<OutputIdentifier>>,
rproof_pmmr: ReadonlyPMMR<'a, RangeProof, PMMRBackend<RangeProof>>,
) -> UTXOView<'a> {
UTXOView {
header_pmmr,
output_pmmr,
rproof_pmmr,
}
}
pub fn validate_block(
&self,
block: &Block,
batch: &Batch<'_>,
) -> Result<Vec<(OutputIdentifier, CommitPos)>, Error> {
for output in block.outputs() {
self.validate_output(output, batch)?;
}
self.validate_inputs(&block.inputs(), batch)
}
pub fn validate_tx(
&self,
tx: &Transaction,
batch: &Batch<'_>,
) -> Result<Vec<(OutputIdentifier, CommitPos)>, Error> {
for output in tx.outputs() {
self.validate_output(output, batch)?;
}
self.validate_inputs(&tx.inputs(), batch)
}
pub fn validate_inputs(
&self,
inputs: &Inputs,
batch: &Batch<'_>,
) -> Result<Vec<(OutputIdentifier, CommitPos)>, Error> {
match inputs {
Inputs::CommitOnly(inputs) => {
let outputs_spent: Result<Vec<_>, Error> = inputs
.iter()
.map(|input| {
self.validate_input(input.commitment(), batch)
.and_then(|(out, pos)| Ok((out, pos)))
})
.collect();
outputs_spent
}
Inputs::FeaturesAndCommit(inputs) => {
let outputs_spent: Result<Vec<_>, Error> = inputs
.iter()
.map(|input| {
self.validate_input(input.commitment(), batch)
.and_then(|(out, pos)| {
if out == input.into() {
Ok((out, pos))
} else {
error!("input mismatch: {:?}, {:?}, {:?}", out, pos, input);
Err(Error::Other("input mismatch".into()))
}
})
})
.collect();
outputs_spent
}
}
}
fn validate_input(
&self,
input: Commitment,
batch: &Batch<'_>,
) -> Result<(OutputIdentifier, CommitPos), Error> {
let pos = batch.get_output_pos_height(&input)?;
if let Some(pos1) = pos {
if let Some(out) = self.output_pmmr.get_data(pos1.pos - 1) {
if out.commitment() == input {
return Ok((out, pos1));
} else {
error!("input mismatch: {:?}, {:?}, {:?}", out, pos1, input);
return Err(Error::Other(
"input mismatch (output_pos index mismatch?)".into(),
));
}
}
}
Err(Error::AlreadySpent(input))
}
fn validate_output(&self, output: &Output, batch: &Batch<'_>) -> Result<(), Error> {
if let Ok(pos0) = batch.get_output_pos(&output.commitment()) {
if let Some(out_mmr) = self.output_pmmr.get_data(pos0) {
if out_mmr.commitment() == output.commitment() {
return Err(Error::DuplicateCommitment(output.commitment()));
}
}
}
Ok(())
}
pub fn get_unspent_output_at(&self, pos0: u64) -> Result<Output, Error> {
match self.output_pmmr.get_data(pos0) {
Some(output_id) => match self.rproof_pmmr.get_data(pos0) {
Some(rproof) => Ok(output_id.into_output(rproof)),
None => Err(Error::RangeproofNotFound),
},
None => Err(Error::OutputNotFound),
}
}
pub fn verify_coinbase_maturity(
&self,
inputs: &Inputs,
height: u64,
batch: &Batch<'_>,
) -> Result<(), Error> {
let inputs: Vec<_> = inputs.into();
let spent: Result<Vec<_>, _> = inputs
.iter()
.map(|x| self.validate_input(x.commitment(), batch))
.collect();
let pos = spent?
.iter()
.filter_map(|(out, pos)| {
if out.features.is_coinbase() {
Some(pos.pos)
} else {
None
}
})
.max();
if let Some(pos) = pos {
if height < global::coinbase_maturity() {
return Err(Error::ImmatureCoinbase);
}
let cutoff_height = height.saturating_sub(global::coinbase_maturity());
let cutoff_header = self.get_header_by_height(cutoff_height, batch)?;
let cutoff_pos = cutoff_header.output_mmr_size;
if pos > cutoff_pos {
return Err(Error::ImmatureCoinbase);
}
}
Ok(())
}
fn get_header_hash(&self, pos1: u64) -> Option<Hash> {
self.header_pmmr.get_data(pos1 - 1).map(|x| x.hash())
}
pub fn get_header_by_height(
&self,
height: u64,
batch: &Batch<'_>,
) -> Result<BlockHeader, Error> {
let pos1 = 1 + pmmr::insertion_to_pmmr_index(height);
if let Some(hash) = self.get_header_hash(pos1) {
let header = batch.get_block_header(&hash)?;
Ok(header)
} else {
Err(Error::Other("get header by height".to_string()))
}
}
}