readonly verify_coinbase_maturity (#2164)

* move verify_coinbase_maturity into utxo_view
no longer need a write lock on txhashset

* rustfmt
This commit is contained in:
Antioch Peverell 2018-12-16 09:26:39 +00:00 committed by GitHub
parent a50dcbfaa5
commit c188b60a38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 65 deletions

View file

@ -506,9 +506,9 @@ impl Chain {
/// that has not yet sufficiently matured.
pub fn verify_coinbase_maturity(&self, tx: &Transaction) -> Result<(), Error> {
let height = self.next_block_height()?;
let mut txhashset = self.txhashset.write();
txhashset::extending_readonly(&mut txhashset, |extension| {
extension.verify_coinbase_maturity(&tx.inputs(), height)?;
let txhashset = self.txhashset.read();
txhashset::utxo_view(&txhashset, |utxo| {
utxo.verify_coinbase_maturity(&tx.inputs(), height)?;
Ok(())
})
}

View file

@ -434,16 +434,10 @@ fn validate_block(block: &Block, ctx: &mut BlockContext<'_>) -> Result<(), Error
Ok(())
}
/// TODO - This can move into the utxo_view.
/// Verify the block is not attempting to spend coinbase outputs
/// before they have sufficiently matured.
/// Note: requires a txhashset extension.
fn verify_coinbase_maturity(
block: &Block,
ext: &mut txhashset::Extension<'_>,
) -> Result<(), Error> {
ext.verify_coinbase_maturity(&block.inputs(), block.header.height)?;
Ok(())
/// Verify the block is not spending coinbase outputs before they have sufficiently matured.
fn verify_coinbase_maturity(block: &Block, ext: &txhashset::Extension<'_>) -> Result<(), Error> {
ext.utxo_view()
.verify_coinbase_maturity(&block.inputs(), block.header.height)
}
/// Some "real magick" verification logic.
@ -649,7 +643,5 @@ pub fn rewind_and_apply_fork(b: &Block, ext: &mut txhashset::Extension<'_>) -> R
}
fn validate_utxo(block: &Block, ext: &txhashset::Extension<'_>) -> Result<(), Error> {
let utxo = ext.utxo_view();
utxo.validate_block(block)?;
Ok(())
ext.utxo_view().validate_block(block)
}

View file

@ -20,7 +20,7 @@ use crate::core::core::hash::{Hash, Hashed};
use crate::core::core::merkle_proof::MerkleProof;
use crate::core::core::pmmr::{self, ReadonlyPMMR, RewindablePMMR, PMMR};
use crate::core::core::{
Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, TxKernel, TxKernelEntry,
Block, BlockHeader, Input, Output, OutputIdentifier, TxKernel, TxKernelEntry,
};
use crate::core::global;
use crate::core::ser::{PMMRIndexHashable, PMMRable};
@ -353,11 +353,13 @@ where
{
let output_pmmr =
ReadonlyPMMR::at(&trees.output_pmmr_h.backend, trees.output_pmmr_h.last_pos);
let header_pmmr =
ReadonlyPMMR::at(&trees.header_pmmr_h.backend, trees.header_pmmr_h.last_pos);
// Create a new batch here to pass into the utxo_view.
// Discard it (rollback) after we finish with the utxo_view.
let batch = trees.commit_index.batch()?;
let utxo = UTXOView::new(output_pmmr, &batch);
let utxo = UTXOView::new(output_pmmr, header_pmmr, &batch);
res = inner(&utxo);
}
res
@ -829,46 +831,11 @@ impl<'a> Extension<'a> {
/// Build a view of the current UTXO set based on the output PMMR.
pub fn utxo_view(&'a self) -> UTXOView<'a> {
UTXOView::new(self.output_pmmr.readonly_pmmr(), self.batch)
}
/// Verify we are not attempting to spend any coinbase outputs
/// that have not sufficiently matured.
pub fn verify_coinbase_maturity(
&mut self,
inputs: &Vec<Input>,
height: u64,
) -> Result<(), Error> {
// Find the greatest output pos of any coinbase
// outputs we are attempting to spend.
let pos = inputs
.iter()
.filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT))
.filter_map(|x| self.batch.get_output_pos(&x.commitment()).ok())
.max()
.unwrap_or(0);
if pos > 0 {
// If we have not yet reached 1,000 / 1,440 blocks then
// we can fail immediately as coinbase cannot be mature.
if height < global::coinbase_maturity() {
return Err(ErrorKind::ImmatureCoinbase.into());
}
// Find the "cutoff" pos in the output MMR based on the
// header from 1,000 blocks ago.
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 any output pos exceed the cutoff_pos
// we know they have not yet sufficiently matured.
if pos > cutoff_pos {
return Err(ErrorKind::ImmatureCoinbase.into());
}
}
Ok(())
UTXOView::new(
self.output_pmmr.readonly_pmmr(),
self.header_pmmr.readonly_pmmr(),
self.batch,
)
}
/// Apply a new block to the existing state.

View file

@ -14,8 +14,10 @@
//! Lightweight readonly view into output MMR for convenience.
use crate::core::core::pmmr::ReadonlyPMMR;
use crate::core::core::{Block, Input, Output, Transaction};
use crate::core::core::hash::Hash;
use crate::core::core::pmmr::{self, ReadonlyPMMR};
use crate::core::core::{Block, BlockHeader, Input, Output, OutputFeatures, Transaction};
use crate::core::global;
use crate::core::ser::PMMRIndexHashable;
use crate::error::{Error, ErrorKind};
use crate::store::Batch;
@ -23,17 +25,23 @@ use grin_store::pmmr::PMMRBackend;
/// Readonly view of the UTXO set (based on output MMR).
pub struct UTXOView<'a> {
pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
output_pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
batch: &'a Batch<'a>,
}
impl<'a> UTXOView<'a> {
/// Build a new UTXO view.
pub fn new(
pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
output_pmmr: ReadonlyPMMR<'a, Output, PMMRBackend<Output>>,
header_pmmr: ReadonlyPMMR<'a, BlockHeader, PMMRBackend<BlockHeader>>,
batch: &'a Batch<'_>,
) -> UTXOView<'a> {
UTXOView { pmmr, batch }
UTXOView {
output_pmmr,
header_pmmr,
batch,
}
}
/// Validate a block against the current UTXO set.
@ -69,7 +77,7 @@ impl<'a> UTXOView<'a> {
// Compare the hash in the output MMR at the expected pos.
fn validate_input(&self, input: &Input) -> Result<(), Error> {
if let Ok(pos) = self.batch.get_output_pos(&input.commitment()) {
if let Some(hash) = self.pmmr.get_hash(pos) {
if let Some(hash) = self.output_pmmr.get_hash(pos) {
if hash == input.hash_with_index(pos - 1) {
return Ok(());
}
@ -81,7 +89,7 @@ impl<'a> UTXOView<'a> {
// Output is valid if it would not result in a duplicate commitment in the output MMR.
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.pmmr.get_data(pos) {
if let Some(out_mmr) = self.output_pmmr.get_data(pos) {
if out_mmr.commitment() == output.commitment() {
return Err(ErrorKind::DuplicateCommitment(output.commitment()).into());
}
@ -89,4 +97,57 @@ impl<'a> UTXOView<'a> {
}
Ok(())
}
/// Verify we are not attempting to spend any coinbase outputs
/// that have not sufficiently matured.
pub fn verify_coinbase_maturity(&self, inputs: &Vec<Input>, height: u64) -> Result<(), Error> {
// Find the greatest output pos of any coinbase
// outputs we are attempting to spend.
let pos = inputs
.iter()
.filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT))
.filter_map(|x| self.batch.get_output_pos(&x.commitment()).ok())
.max()
.unwrap_or(0);
if pos > 0 {
// If we have not yet reached 1,000 / 1,440 blocks then
// we can fail immediately as coinbase cannot be mature.
if height < global::coinbase_maturity() {
return Err(ErrorKind::ImmatureCoinbase.into());
}
// Find the "cutoff" pos in the output MMR based on the
// header from 1,000 blocks ago.
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 any output pos exceed the cutoff_pos
// we know they have not yet sufficiently matured.
if pos > cutoff_pos {
return Err(ErrorKind::ImmatureCoinbase.into());
}
}
Ok(())
}
/// Get the header hash for the specified pos from the underlying MMR backend.
fn get_header_hash(&self, pos: u64) -> Option<Hash> {
self.header_pmmr.get_data(pos).map(|x| x.hash())
}
/// Get the header at the specified height based on the current state of the extension.
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash.
/// Looks the header up in the db by 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())
}
}
}