mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
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:
parent
a50dcbfaa5
commit
c188b60a38
4 changed files with 85 additions and 65 deletions
|
@ -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(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue