-
Notifications
You must be signed in to change notification settings - Fork 992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce CommitOnly variant of Inputs #3419
Changes from 15 commits
496a698
d6854f8
f4dcfb2
0d152f3
3a68648
0414191
4abd8fd
dae8a6d
cbfec31
758cc1b
a3c3af9
610aa0b
7835989
58d9760
a5be223
b5dae01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ use crate::types::{ | |
BlockStatus, ChainAdapter, CommitPos, NoStatus, Options, Tip, TxHashsetWriteStatus, | ||
}; | ||
use crate::util::secp::pedersen::{Commitment, RangeProof}; | ||
use crate::util::RwLock; | ||
use crate::{util::RwLock, ChainStore}; | ||
use grin_store::Error::NotFoundErr; | ||
use std::collections::HashMap; | ||
use std::fs::{self, File}; | ||
|
@@ -171,6 +171,10 @@ impl Chain { | |
) -> Result<Chain, Error> { | ||
let store = Arc::new(store::ChainStore::new(&db_root)?); | ||
|
||
// DB migrations to be run prior to the chain being used. | ||
// Migrate full blocks to protocol version v3. | ||
Chain::migrate_db_v2_v3(&store)?; | ||
|
||
// open the txhashset, creating a new one if necessary | ||
let mut txhashset = txhashset::TxHashSet::open(db_root.clone(), store.clone(), None)?; | ||
|
||
|
@@ -218,12 +222,6 @@ impl Chain { | |
genesis: genesis.header, | ||
}; | ||
|
||
// DB migrations to be run prior to the chain being used. | ||
{ | ||
// Migrate full blocks to protocol version v2. | ||
chain.migrate_db_v1_v2()?; | ||
} | ||
|
||
chain.log_heads()?; | ||
|
||
Ok(chain) | ||
|
@@ -275,11 +273,12 @@ impl Chain { | |
/// We also need to support relaying blocks with FeaturesAndCommit inputs to peers. | ||
/// So we need a way to convert blocks from CommitOnly to FeaturesAndCommit. | ||
/// Validating the inputs against the utxo_view allows us to look the outputs up. | ||
fn convert_block_v2(&self, block: Block) -> Result<Block, Error> { | ||
pub fn convert_block_v2(&self, block: Block) -> Result<Block, Error> { | ||
debug!( | ||
"convert_block_v2: {} at {}", | ||
"convert_block_v2: {} at {} ({} -> v2)", | ||
block.header.hash(), | ||
block.header.height | ||
block.header.height, | ||
block.inputs().version_str(), | ||
); | ||
|
||
if block.inputs().is_empty() { | ||
|
@@ -291,18 +290,19 @@ impl Chain { | |
|
||
let mut header_pmmr = self.header_pmmr.write(); | ||
let mut txhashset = self.txhashset.write(); | ||
let outputs = | ||
let inputs: Vec<_> = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would find it easier to ready if |
||
txhashset::extending_readonly(&mut header_pmmr, &mut txhashset, |ext, batch| { | ||
let previous_header = batch.get_previous_header(&block.header)?; | ||
pipe::rewind_and_apply_fork(&previous_header, ext, batch)?; | ||
ext.extension | ||
.utxo_view(ext.header_extension) | ||
.validate_inputs(block.inputs(), batch) | ||
.map(|outputs| outputs.into_iter().map(|(out, _)| out).collect()) | ||
})?; | ||
let outputs: Vec<_> = outputs.into_iter().map(|(out, _)| out).collect(); | ||
let inputs = inputs.as_slice().into(); | ||
Ok(Block { | ||
header: block.header, | ||
body: block.body.replace_inputs(outputs.into()), | ||
body: block.body.replace_inputs(inputs), | ||
}) | ||
} | ||
|
||
|
@@ -397,8 +397,9 @@ impl Chain { | |
// Only do this once we know the header PoW is valid. | ||
self.check_orphan(&b, opts)?; | ||
|
||
// Convert block to FeaturesAndCommit inputs. | ||
// We know this block is not an orphan and header is valid at this point. | ||
// We can only reliably convert to "v2" if not an orphan (may spend output from previous block). | ||
// We convert from "v3" to "v2" by looking up outputs to be spent. | ||
// This conversion also ensures a block received in "v2" has valid input features (prevents malleability). | ||
let b = self.convert_block_v2(b)?; | ||
|
||
let (maybe_new_head, prev_head) = { | ||
|
@@ -662,12 +663,12 @@ impl Chain { | |
|
||
/// Verify we are not attempting to spend a coinbase output | ||
/// that has not yet sufficiently matured. | ||
pub fn verify_coinbase_maturity(&self, tx: &Transaction) -> Result<(), Error> { | ||
pub fn verify_coinbase_maturity(&self, inputs: Inputs) -> Result<(), Error> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering if is it any better to change the function signature to: pub fn verify_coinbase_maturity(&self, inputs: &Inputs) -> Result<(), Error> { In the current case Rust makes a copy of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 I think this makes sense. Same for |
||
let height = self.next_block_height()?; | ||
let header_pmmr = self.header_pmmr.read(); | ||
let txhashset = self.txhashset.read(); | ||
txhashset::utxo_view(&header_pmmr, &txhashset, |utxo, batch| { | ||
utxo.verify_coinbase_maturity(&tx.inputs(), height, batch)?; | ||
utxo.verify_coinbase_maturity(inputs, height, batch)?; | ||
Ok(()) | ||
}) | ||
} | ||
|
@@ -1403,14 +1404,13 @@ impl Chain { | |
self.header_pmmr.read().get_header_hash_by_height(height) | ||
} | ||
|
||
/// Migrate our local db from v1 to v2. | ||
/// This covers blocks which themselves contain transactions. | ||
/// Transaction kernels changed in v2 due to "variable size kernels". | ||
fn migrate_db_v1_v2(&self) -> Result<(), Error> { | ||
let store_v1 = self.store.with_version(ProtocolVersion(1)); | ||
let batch = store_v1.batch()?; | ||
/// Migrate our local db from v2 to v3. | ||
/// "commit only" inputs. | ||
fn migrate_db_v2_v3(store: &ChainStore) -> Result<(), Error> { | ||
let store_v2 = store.with_version(ProtocolVersion(2)); | ||
let batch = store_v2.batch()?; | ||
for (_, block) in batch.blocks_iter()? { | ||
batch.migrate_block(&block, ProtocolVersion(2))?; | ||
batch.migrate_block(&block, ProtocolVersion(3))?; | ||
} | ||
batch.commit()?; | ||
Ok(()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,16 @@ impl<'a> UTXOView<'a> { | |
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() | ||
|
@@ -93,6 +103,7 @@ impl<'a> UTXOView<'a> { | |
if out == input.into() { | ||
Ok((out, pos)) | ||
} else { | ||
error!("input mismatch: {:?}, {:?}, {:?}", out, pos, input); | ||
Err(ErrorKind::Other("input mismatch".into()).into()) | ||
} | ||
Comment on lines
103
to
108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So is this check redundant based on what we have at line 128-136 or the OutputFeatures feature check is important (for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the latter. We need to ensure that if the features are passed in that they are correct. This is to maintain existing behavior and to avoid introducing malleability with the features being effectively ignored. |
||
}) | ||
|
@@ -114,7 +125,15 @@ impl<'a> UTXOView<'a> { | |
let pos = batch.get_output_pos_height(&input)?; | ||
if let Some(pos) = pos { | ||
if let Some(out) = self.output_pmmr.get_data(pos.pos) { | ||
return Ok((out, pos)); | ||
if out.commitment() == input { | ||
return Ok((out, pos)); | ||
} else { | ||
error!("input mismatch: {:?}, {:?}, {:?}", out, pos, input); | ||
return Err(ErrorKind::Other( | ||
"input mismatch (output_pos index mismatch?)".into(), | ||
) | ||
.into()); | ||
} | ||
} | ||
} | ||
Err(ErrorKind::AlreadySpent(input).into()) | ||
|
@@ -147,21 +166,31 @@ impl<'a> UTXOView<'a> { | |
/// that have not sufficiently matured. | ||
pub fn verify_coinbase_maturity( | ||
&self, | ||
inputs: &Inputs, | ||
inputs: Inputs, | ||
antiochp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
height: u64, | ||
batch: &Batch<'_>, | ||
) -> Result<(), Error> { | ||
// Find the greatest output pos of any coinbase | ||
// outputs we are attempting to spend. | ||
let inputs: Vec<_> = inputs.into(); | ||
let pos = inputs | ||
|
||
// Lookup the outputs being spent. | ||
let spent: Result<Vec<_>, _> = inputs | ||
.iter() | ||
.filter(|x| x.is_coinbase()) | ||
.filter_map(|x| batch.get_output_pos(&x.commitment()).ok()) | ||
.max() | ||
.unwrap_or(0); | ||
.map(|x| self.validate_input(x.commitment(), batch)) | ||
.collect(); | ||
|
||
if pos > 0 { | ||
// Find the max pos of any coinbase being spent. | ||
let pos = spent? | ||
.iter() | ||
.filter_map(|(out, pos)| { | ||
if out.features.is_coinbase() { | ||
Some(pos.pos) | ||
} else { | ||
None | ||
} | ||
}) | ||
.max(); | ||
antiochp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if let Some(pos) = pos { | ||
// If we have not yet reached 1440 blocks then | ||
// we can fail immediately as coinbase cannot be mature. | ||
if height < global::coinbase_maturity() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why don't need a lexical scope anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was purely for visual/readability purposes before. It was not needed at the code level.