Skip to content

Commit

Permalink
fix: allow 0-conf in blockchain db
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Jan 3, 2022
1 parent f5274aa commit 38f7c2f
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 134 deletions.
2 changes: 1 addition & 1 deletion base_layer/core/src/chain_storage/blockchain_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ where B: BlockchainBackend

if block_add_result.was_chain_modified() {
// If blocks were added and the node is in pruned mode, perform pruning
prune_database_if_needed(&mut *db, self.config.pruning_horizon, self.config.pruning_interval)?
prune_database_if_needed(&mut *db, self.config.pruning_horizon, self.config.pruning_interval)?;
}

info!(
Expand Down
64 changes: 48 additions & 16 deletions base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ use crate::{
lmdb_replace,
},
TransactionInputRowData,
TransactionInputRowDataRef,
TransactionKernelRowData,
TransactionOutputRowData,
},
Expand Down Expand Up @@ -638,15 +639,20 @@ impl LMDBDatabase {
txn: &WriteTransaction<'_>,
height: u64,
header_hash: HashOutput,
input: TransactionInput,
input: &TransactionInput,
mmr_position: u32,
) -> Result<(), ChainStorageError> {
lmdb_delete(
txn,
&self.utxo_commitment_index,
input.commitment().as_bytes(),
"utxo_commitment_index",
)?;
)
.or_else(|err| match err {
// The commitment may not yet be included in the DB in the 0-conf transaction case
ChainStorageError::ValueNotFound { .. } => Ok(()),
_ => Err(err),
})?;
lmdb_insert(
txn,
&self.deleted_txo_mmr_position_to_height_index,
Expand Down Expand Up @@ -680,6 +686,7 @@ impl LMDBDatabase {
});
}

// TODO: 0-conf is not currently supported for transactions with unique_id set
lmdb_delete(txn, &self.unique_id_index, key.as_bytes(), "unique_id_index")?;
key.set_deleted_height(height);
debug!(
Expand Down Expand Up @@ -707,7 +714,7 @@ impl LMDBDatabase {
txn,
&*self.inputs_db,
key.as_bytes(),
&TransactionInputRowData {
&TransactionInputRowDataRef {
input,
header_hash,
mmr_position,
Expand Down Expand Up @@ -1169,11 +1176,36 @@ impl LMDBDatabase {
let mut output_mmr = MutableMmr::<HashDigest, _>::new(pruned_output_set, Bitmap::create())?;
let mut witness_mmr = MerkleMountainRange::<HashDigest, _>::new(pruned_proof_set);

let leaf_count = witness_mmr.get_leaf_count()?;

// Output hashes added before inputs so that inputs can spend outputs in this transaction (0-conf and combined)
let outputs = outputs
.into_iter()
.enumerate()
.map(|(i, output)| {
output_mmr.push(output.hash())?;
witness_mmr.push(output.witness_hash())?;
Ok((output, leaf_count + i + 1))
})
.collect::<Result<Vec<_>, ChainStorageError>>()?;

let mut spent_zero_conf_commitments = Vec::new();
// unique_id_index expects inputs to be inserted before outputs
for input in inputs {
let index = self
.fetch_mmr_leaf_index(&**txn, MmrTree::Utxo, &input.output_hash())?
.ok_or(ChainStorageError::UnspendableInput)?;
for input in &inputs {
let index = match self.fetch_mmr_leaf_index(&**txn, MmrTree::Utxo, &input.output_hash())? {
Some(index) => index,
None => match output_mmr.find_leaf_index(&input.output_hash())? {
Some(index) => {
debug!(
target: LOG_TARGET,
"Input {} spends output from current block (0-conf)", input
);
spent_zero_conf_commitments.push(&input.commitment);
index
},
None => return Err(ChainStorageError::UnspendableInput),
},
};
if !output_mmr.delete(index) {
return Err(ChainStorageError::InvalidOperation(format!(
"Could not delete index {} from the output MMR",
Expand All @@ -1184,19 +1216,19 @@ impl LMDBDatabase {
self.insert_input(txn, current_header_at_height.height, block_hash.clone(), input, index)?;
}

for output in outputs {
output_mmr.push(output.hash())?;
witness_mmr.push(output.witness_hash())?;
for (output, mmr_count) in outputs {
debug!(target: LOG_TARGET, "Inserting output `{}`", output.commitment.to_hex());
self.insert_output(
self.insert_output(txn, &block_hash, header.height, &output, mmr_count as u32 - 1)?;
}

for commitment in spent_zero_conf_commitments {
lmdb_delete(
txn,
&block_hash,
header.height,
&output,
(witness_mmr.get_leaf_count()? - 1) as u32,
&self.utxo_commitment_index,
commitment.as_bytes(),
"utxo_commitment_index",
)?;
}

// Merge current deletions with the tip bitmap
let deleted_at_current_height = output_mmr.deleted().clone();
// Merge the new indexes with the blockchain deleted bitmap
Expand Down
8 changes: 8 additions & 0 deletions base_layer/core/src/chain_storage/lmdb_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ pub(crate) struct TransactionOutputRowData {
pub mined_height: u64,
}

#[derive(Serialize, Debug)]
pub(crate) struct TransactionInputRowDataRef<'a> {
pub input: &'a TransactionInput,
pub header_hash: HashOutput,
pub mmr_position: u32,
pub hash: HashOutput,
}

#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct TransactionInputRowData {
pub input: TransactionInput,
Expand Down
117 changes: 14 additions & 103 deletions base_layer/core/src/chain_storage/tests/blockchain_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use std::sync::Arc;

use rand::rngs::OsRng;
use tari_common_types::types::PublicKey;
use tari_crypto::keys::PublicKey as PublicKeyTrait;
use tari_test_utils::unpack_enum;
use tari_utilities::Hashable;
Expand All @@ -39,7 +40,7 @@ use crate::{
transactions::{
tari_amount::T,
test_helpers::{schema_to_transaction, TransactionSchema},
transaction::{OutputFeatures, Transaction, UnblindedOutput},
transaction::{OutputFeatures, OutputFlags, Transaction, UnblindedOutput},
},
txn_schema,
};
Expand Down Expand Up @@ -375,13 +376,11 @@ mod fetch_block_hashes_from_header_tip {
}

mod add_block {
use tari_common_types::types::PublicKey;
use tari_utilities::hex::Hex;

use super::*;
use crate::{transactions::transaction::OutputFlags, validation::ValidationError};

#[test]
#[ignore = "broken after validator node merge"]
fn it_rejects_duplicate_commitments_in_the_utxo_set() {
let db = setup();
let (blocks, outputs) = add_many_chained_blocks(5, &db);
Expand All @@ -407,14 +406,12 @@ mod add_block {
script: tari_crypto::script![Nop],
input_data: None,
}]);
let commitment_hex = txns[0].body.outputs()[0].commitment.to_hex();

let (block, _) = create_next_block(&db, &prev_block, txns);
let err = db.add_block(block.clone()).unwrap_err();
unpack_enum!(
ChainStorageError::ValidationError {
source: ValidationError::ContainsTxO
} = err
);
unpack_enum!(ChainStorageError::KeyExists { key, .. } = err);
assert_eq!(key, commitment_hex);
// Check rollback
let header = db.fetch_header(block.header.height).unwrap();
assert!(header.is_none());
Expand Down Expand Up @@ -481,91 +478,6 @@ mod add_block {
let (block, _) = create_next_block(&db, prev_block, transactions);
db.add_block(block).unwrap().assert_added();
}

#[test]
#[ignore = "broken after validator node merge"]
fn it_rejects_duplicate_mint_or_burn_transactions_per_unique_id() {
let db = setup();
let (blocks, outputs) = add_many_chained_blocks(1, &db);

let prev_block = blocks.last().unwrap();

let (_, asset_pk) = PublicKey::random_keypair(&mut OsRng);
let unique_id = vec![1u8; 3];
let features = OutputFeatures::for_minting(asset_pk.clone(), Default::default(), unique_id.clone(), None);
let (txns, _) = schema_to_transaction(&[txn_schema!(
from: vec![outputs[0].clone()],
to: vec![10 * T, 10 * T],
features: features
)]);

let (block, _) = create_next_block(&db, prev_block, txns);
let err = db.add_block(block).unwrap_err();

unpack_enum!(
ChainStorageError::ValidationError {
source: ValidationError::ContainsDuplicateUtxoUniqueID
} = err
);

let features = OutputFeatures {
flags: OutputFlags::BURN_NON_FUNGIBLE,
parent_public_key: Some(asset_pk),
unique_id: Some(unique_id),
..Default::default()
};
let (txns, _) = schema_to_transaction(&[txn_schema!(
from: vec![outputs[0].clone()],
to: vec![10 * T, 10 * T],
features: features
)]);

let (block, _) = create_next_block(&db, prev_block, txns);
let err = db.add_block(block).unwrap_err();

unpack_enum!(
ChainStorageError::ValidationError {
source: ValidationError::ContainsDuplicateUtxoUniqueID
} = err
);
}

#[test]
#[ignore = "broken after validator node merge"]
fn it_rejects_duplicate_mint_or_burn_transactions_in_blockchain() {
let db = setup();
let (blocks, outputs) = add_many_chained_blocks(1, &db);

let prev_block = blocks.last().unwrap();

let (_, asset_pk) = PublicKey::random_keypair(&mut OsRng);
let unique_id = vec![1u8; 3];
let features = OutputFeatures::for_minting(asset_pk.clone(), Default::default(), unique_id.clone(), None);
let (txns, outputs) = schema_to_transaction(&[txn_schema!(
from: vec![outputs[0].clone()],
to: vec![10 * T],
features: features
)]);

let (block, _) = create_next_block(&db, prev_block, txns);
db.add_block(block.clone()).unwrap().assert_added();

let features = OutputFeatures::for_minting(asset_pk, Default::default(), unique_id, None);
let (txns, _) = schema_to_transaction(&[txn_schema!(
from: vec![outputs[0].clone()],
to: vec![T],
features: features
)]);

let (block, _) = create_next_block(&db, &block, txns);
let err = db.add_block(block).unwrap_err();

unpack_enum!(
ChainStorageError::ValidationError {
source: ValidationError::ContainsDuplicateUtxoUniqueID
} = err
);
}
}

mod get_stats {
Expand All @@ -583,14 +495,13 @@ mod fetch_total_size_stats {
use super::*;

#[test]
#[ignore = "broken after validator node merge"]
fn it_measures_the_number_of_entries() {
let db = setup();
let _ = add_many_chained_blocks(2, &db);
let stats = db.fetch_total_size_stats().unwrap();
assert_eq!(
stats.sizes().iter().find(|s| s.name == "utxos_db").unwrap().num_entries,
2
3
);
}
}
Expand Down Expand Up @@ -734,28 +645,29 @@ mod clear_all_pending_headers {
}

#[test]
#[ignore = "broken after validator node merge"]
fn it_clears_headers_after_tip() {
let db = setup();
let _ = add_many_chained_blocks(2, &db);
let prev_block = db.fetch_block(2).unwrap();
let mut prev_accum = prev_block.accumulated_data.clone();
let mut prev_block = Arc::new(prev_block.try_into_block().unwrap());
let mut prev_header = prev_block.try_into_chain_block().unwrap().to_chain_header();
let headers = (0..5)
.map(|_| {
let (block, _) = create_next_block(&db, &prev_block, vec![]);
let mut header = BlockHeader::from_previous(prev_header.header());
header.kernel_mmr_size += 1;
header.output_mmr_size += 1;
let accum = BlockHeaderAccumulatedData::builder(&prev_accum)
.with_hash(block.hash())
.with_hash(header.hash())
.with_achieved_target_difficulty(
AchievedTargetDifficulty::try_construct(PowAlgorithm::Sha3, 0.into(), 0.into()).unwrap(),
)
.with_total_kernel_offset(Default::default())
.build()
.unwrap();

let header = ChainHeader::try_construct(block.header.clone(), accum.clone()).unwrap();
let header = ChainHeader::try_construct(header.clone(), accum.clone()).unwrap();

prev_block = block;
prev_header = header.clone();
prev_accum = accum;
header
})
Expand Down Expand Up @@ -786,7 +698,6 @@ mod fetch_utxo_by_unique_id {
}

#[test]
#[ignore = "broken after validator node merge"]
fn it_finds_the_utxo_by_unique_id_at_deleted_height() {
let db = setup();
let unique_id = vec![1u8; 3];
Expand Down
21 changes: 19 additions & 2 deletions base_layer/core/src/test_helpers/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ pub fn create_test_db() -> TempDatabase {
pub struct TempDatabase {
path: PathBuf,
db: Option<LMDBDatabase>,
delete_on_drop: bool,
}

impl TempDatabase {
Expand All @@ -159,8 +160,22 @@ impl TempDatabase {
Self {
db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()),
path: temp_path,
delete_on_drop: true,
}
}

pub fn from_path<P: AsRef<Path>>(temp_path: P) -> Self {
Self {
db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()),
path: temp_path.as_ref().to_path_buf(),
delete_on_drop: true,
}
}

pub fn disable_delete_on_drop(&mut self) -> &mut Self {
self.delete_on_drop = false;
self
}
}

impl Default for TempDatabase {
Expand All @@ -181,8 +196,10 @@ impl Drop for TempDatabase {
fn drop(&mut self) {
// force a drop on the LMDB db
self.db = None;
if Path::new(&self.path).exists() {
fs::remove_dir_all(&self.path).expect("Could not delete temporary file");
if self.delete_on_drop {
if Path::new(&self.path).exists() {
fs::remove_dir_all(&self.path).expect("Could not delete temporary file");
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion base_layer/core/src/validation/block_validators/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ mod unique_id {
}

#[tokio::test]
#[ignore = "broken after validator node merge"]
async fn it_allows_spending_to_new_utxo() {
let (mut blockchain, validator) = setup();

Expand Down
Loading

0 comments on commit 38f7c2f

Please sign in to comment.