Skip to content
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

fix: chain error caused by zero-conf transactions and reorgs #3223

Merged
merged 3 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 42 additions & 29 deletions base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,29 +744,63 @@ impl LMDBDatabase {
"block_accumulated_data_db",
)?;

let output_rows =
lmdb_delete_keys_starting_with::<TransactionOutputRowData>(&write_txn, &self.utxos_db, &hash_hex)?;
self.delete_block_inputs_outputs(write_txn, &hash_hex)?;
self.delete_block_kernels(write_txn, &hash_hex)?;

Ok(())
}

fn delete_block_inputs_outputs(&self, txn: &WriteTransaction<'_>, hash: &str) -> Result<(), ChainStorageError> {
let output_rows = lmdb_delete_keys_starting_with::<TransactionOutputRowData>(txn, &self.utxos_db, hash)?;
debug!(target: LOG_TARGET, "Deleted {} outputs...", output_rows.len());
let inputs = lmdb_delete_keys_starting_with::<TransactionInputRowData>(txn, &self.inputs_db, hash)?;
debug!(target: LOG_TARGET, "Deleted {} input(s)...", inputs.len());

for utxo in &output_rows {
trace!(target: LOG_TARGET, "Deleting UTXO `{}`", to_hex(&utxo.hash));
lmdb_delete(
&write_txn,
txn,
&self.txos_hash_to_index_db,
utxo.hash.as_slice(),
"txos_hash_to_index_db",
)?;
if let Some(ref output) = utxo.output {
let output_hash = output.hash();
// if an output was already spent in the block, it was never created as unspent, so dont delete it as it
// does not exist here
if inputs.iter().any(|r| r.input.output_hash() == output_hash) {
continue;
}
lmdb_delete(
&write_txn,
txn,
&*self.utxo_commitment_index,
output.commitment.as_bytes(),
"utxo_commitment_index",
)?;
}
}
let kernels =
lmdb_delete_keys_starting_with::<TransactionKernelRowData>(&write_txn, &self.kernels_db, &hash_hex)?;
// Move inputs in this block back into the unspent set, any outputs spent within this block they will be removed
// by deleting all the block's outputs below
for row in inputs {
// If input spends an output in this block, don't add it to the utxo set
let output_hash = row.input.output_hash();
if output_rows.iter().any(|r| r.hash == output_hash) {
continue;
}
trace!(target: LOG_TARGET, "Input moved to UTXO set: {}", row.input);
lmdb_insert(
txn,
&*self.utxo_commitment_index,
row.input.commitment.as_bytes(),
&row.input.output_hash(),
"utxo_commitment_index",
)?;
}
Ok(())
}

fn delete_block_kernels(&self, txn: &WriteTransaction<'_>, hash: &str) -> Result<(), ChainStorageError> {
let kernels = lmdb_delete_keys_starting_with::<TransactionKernelRowData>(txn, &self.kernels_db, hash)?;
debug!(target: LOG_TARGET, "Deleted {} kernels...", kernels.len());
for kernel in kernels {
trace!(
Expand All @@ -775,7 +809,7 @@ impl LMDBDatabase {
kernel.kernel.excess.to_hex()
);
lmdb_delete(
&write_txn,
txn,
&self.kernel_excess_index,
kernel.kernel.excess.as_bytes(),
"kernel_excess_index",
Expand All @@ -789,33 +823,12 @@ impl LMDBDatabase {
to_hex(&excess_sig_key)
);
lmdb_delete(
&write_txn,
txn,
&self.kernel_excess_sig_index,
excess_sig_key.as_slice(),
"kernel_excess_sig_index",
)?;
}

let inputs = lmdb_delete_keys_starting_with::<TransactionInputRowData>(&write_txn, &self.inputs_db, &hash_hex)?;
debug!(target: LOG_TARGET, "Deleted {} input(s)...", inputs.len());
// Move inputs in this block back into the unspent set, any outputs spent within this block they will be removed
// by deleting all the block's outputs below
for row in inputs {
// If input spends an output in this block, don't add it to the utxo set
let output_hash = row.input.output_hash();
if output_rows.iter().any(|r| r.hash == output_hash) {
continue;
}
trace!(target: LOG_TARGET, "Input moved to UTXO set: {}", row.input);
lmdb_insert(
&write_txn,
&*self.utxo_commitment_index,
row.input.commitment.as_bytes(),
&row.input.output_hash(),
"utxo_commitment_index",
)?;
}

Ok(())
}

Expand Down
43 changes: 43 additions & 0 deletions integration_tests/features/Reorgs.feature
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,49 @@ Feature: Reorgs
When I submit transaction TX2 to PNODE1
Then PNODE1 has TX2 in MEMPOOL state

@critical @reorg
Scenario: Zero-conf reorg with spending
Given I have a base node NODE1 connected to all seed nodes
Given I have a base node NODE2 connected to node NODE1
When I mine 14 blocks on NODE1
When I mine a block on NODE1 with coinbase CB1
When I mine 4 blocks on NODE1
When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 100
When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 100
When I submit transaction TX1 to NODE1
When I submit transaction TX11 to NODE1
When I mine 1 blocks on NODE1
Then NODE1 has TX1 in MINED state
And NODE1 has TX11 in MINED state
And all nodes are at height 20
And I stop node NODE1
And node NODE2 is at height 20
When I mine a block on NODE2 with coinbase CB2
When I mine 3 blocks on NODE2
When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 100
When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 100
When I submit transaction TX2 to NODE2
When I submit transaction TX21 to NODE2
When I mine 1 blocks on NODE2
Then node NODE2 is at height 25
And NODE2 has TX2 in MINED state
And NODE2 has TX21 in MINED state
And I stop node NODE2
When I start base node NODE1
And node NODE1 is at height 20
When I mine a block on NODE1 with coinbase CB3
When I mine 3 blocks on NODE1
When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 100
When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 100
When I submit transaction TX3 to NODE1
When I submit transaction TX31 to NODE1
When I mine 1 blocks on NODE1
Then NODE1 has TX3 in MINED state
And NODE1 has TX31 in MINED state
And node NODE1 is at height 25
When I start base node NODE2
Then all nodes are on the same chain tip

Scenario Outline: Massive multiple reorg
#
# Chain 1a:
Expand Down