Skip to content

Commit

Permalink
feat: add validation for zero confirmation block sync (#6237)
Browse files Browse the repository at this point in the history
Description
---
Added validation checks to ensure an input in a block that is also an
output in the same block will be accepted when syncing blocks.

Motivation and Context
---
Blocks with zero confirmation inputs and outputs could not be synced
with block sync.

Previous for block `#3316` and input/output
`00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425`:
```rust
2024-03-26 11:40:08.989607400 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425
2024-03-26 11:40:08.989617600 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425 NOT found in index
2024-03-26 11:40:08.991003900 [c::cs::lmdb_db::lmdb_db] TRACE [apply_db_transaction] WriteOperation: Delete orphan with hash: 8a0ac6b1581eedffffcde3b05613b5ec525cd0575cb5a93a5f2bcbe0917957f6
2024-03-26 11:40:08.991035700 [c::cs::lmdb_db::lmdb_db] DEBUG delete_orphan: request to delete orphan block 8a0ac6b1581eedffffcde3b05613b5ec525cd0575cb5a93a5f2bcbe0917957f6 that was not found.
2024-03-26 11:40:08.991047800 [c::cs::lmdb_db::lmdb_db] TRACE [apply_db_transaction] WriteOperation: Insert bad block #3316 8a0ac6b1581eedffffcde3b05613b5ec525cd0575cb5a93a5f2bcbe0917957f6
2024-03-26 11:40:08.991068000 [c::cs::lmdb_db::lmdb_db] DEBUG Cleaned out 0 stale bad blocks
2024-03-26 11:40:08.993916100 [c::cs::lmdb_db::lmdb_db] TRACE Database completed 2 operation(s) in 3ms
2024-03-26 11:40:08.996400500 [c::bn::block_sync] WARN  Block validation failed: Contains an unknown input
2024-03-26 11:40:08.997329500 [c::bn::block_sync] WARN  Block validation failed: Contains an unknown input
2024-03-26 11:40:08.997882300 [c::bn::sync] DEBUG Sync peer 7dcc333714f9b31ebda93edec1 removed from the sync peer list because Contains an unknown input
2024-03-26 11:40:08.997900900 [c::bn::sync] WARN  Banned sync peer 7dcc333714f9b31ebda93edec1 for 7200s because Contains an unknown input
2024-03-26 11:40:08.997914500 [c::bn::block_sync] WARN  Block sync failed: No more sync peers available: Block sync failed
```
This PR:
```rust
2024-03-26 13:57:51.006006700 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425
2024-03-26 13:57:51.006025600 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425 NOT found in index
2024-03-26 13:57:51.008880000 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00f6bce621663a88a0dd0a156d3d4adabb7fc7f38077052f54da5297c52def10
2024-03-26 13:57:51.008910600 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00f6bce621663a88a0dd0a156d3d4adabb7fc7f38077052f54da5297c52def10 Found (0ee73f94629ecacd0d9118d5852e42831b8f71a0ce56e2b18cc20506b1d3135c00f6bce621663a88a0dd0a156d3d4adabb7fc7f38077052f54da5297c52def10)
...
2024-03-26 13:57:51.463387100 [c::cs::lmdb_db::lmdb_db] DEBUG Fetch output: 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425 NOT found in index
2024-03-26 13:57:51.463541300 [c::val::helpers] WARN  Input: (6c8f19486e43c051fb9f308d5eaa6b06a726d0cf1c17c713453d904faf405c56, 00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425) does not exist in the database yet
...
2024-03-26 13:57:55.618675800 [c::cs::lmdb_db::lmdb_db] TRACE Inserting output (`6c8f19486e43c051fb9f308d5eaa6b06a726d0cf1c17c713453d904faf405c56`, `00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425`)
2024-03-26 13:57:55.618713200 [c::cs::lmdb_db::lmdb] TRACE Inserted 72 bytes with key '00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425' into 'txos_hash_to_index_db'
2024-03-26 13:57:55.618729500 [c::cs::lmdb_db::lmdb] TRACE Inserted 1121 bytes with key '8a0ac6b1581eedffffcde3b05613b5ec525cd0575cb5a93a5f2bcbe0917957f600eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425' into 'utxos_db'
2024-03-26 13:57:55.745300700 [c::cs::lmdb_db::lmdb_db] TRACE Inserting input (`6c8f19486e43c051fb9f308d5eaa6b06a726d0cf1c17c713453d904faf405c56`, `00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425`)
2024-03-26 13:57:55.745324500 [c::cs::lmdb_db::lmdb] TRACE Inserted 72 bytes with key '00eaffe31d4c535fb678248078524f9b91f183ca344e6ed52c4ca56f6e50a425' into 'deleted_txo_hash_to_header_index'
```

How Has This Been Tested?
---
System-level testing

What process can a PR reviewer use to test or verify this change?
---
Code review

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->

Co-authored-by: SW van Heerden <swvheerden@gmail.com>
  • Loading branch information
hansieodendaal and SWvheerden committed Apr 2, 2024
1 parent 148e398 commit 55077ce
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 18 deletions.
Expand Up @@ -110,27 +110,44 @@ fn validate_input_not_pruned<B: BlockchainBackend>(
db: &B,
) -> Result<Vec<TransactionInput>, ValidationError> {
let mut inputs: Vec<TransactionInput> = body.inputs().clone();
let outputs: Vec<TransactionOutput> = body.outputs().clone();
for input in &mut inputs {
if input.is_compact() {
let output_mined_info = db
.fetch_output(&input.output_hash())?
.ok_or(ValidationError::UnknownInput)?;
let output = match db.fetch_output(&input.output_hash()) {
Ok(val) => match val {
Some(output_mined_info) => output_mined_info.output,
None => {
let input_output_hash = input.output_hash();
if let Some(found) = outputs.iter().find(|o| o.hash() == input_output_hash) {
found.clone()
} else {
warn!(
target: LOG_TARGET,
"Input not found in database or block, commitment: {}, hash: {}",
input.commitment()?.to_hex(), input_output_hash.to_hex()
);
return Err(ValidationError::UnknownInput);
}
},
},
Err(e) => return Err(ValidationError::from(e)),
};

let rp_hash = match output_mined_info.output.proof {
let rp_hash = match output.proof {
Some(proof) => proof.hash(),
None => FixedHash::zero(),
};
input.add_output_data(
output_mined_info.output.version,
output_mined_info.output.features,
output_mined_info.output.commitment,
output_mined_info.output.script,
output_mined_info.output.sender_offset_public_key,
output_mined_info.output.covenant,
output_mined_info.output.encrypted_data,
output_mined_info.output.metadata_signature,
output.version,
output.features,
output.commitment,
output.script,
output.sender_offset_public_key,
output.covenant,
output.encrypted_data,
output.metadata_signature,
rp_hash,
output_mined_info.output.minimum_value_promise,
output.minimum_value_promise,
);
}
}
Expand Down Expand Up @@ -207,11 +224,16 @@ fn check_inputs_are_utxos<B: BlockchainBackend>(db: &B, body: &AggregateBody) ->
}

let output_hashes = output_hashes.as_ref().unwrap();
let output_hash = input.output_hash();
if output_hashes.iter().any(|output| output == &output_hash) {
let input_output_hash = input.output_hash();
if output_hashes.iter().any(|val| val == &input_output_hash) {
continue;
}
not_found_inputs.push(output_hash);
warn!(
target: LOG_TARGET,
"Input not found in database, commitment: {}, hash: {}",
input.commitment()?.to_hex(), input_output_hash.to_hex()
);
not_found_inputs.push(input_output_hash);
},
Err(err) => {
return Err(err);
Expand Down
5 changes: 3 additions & 2 deletions base_layer/core/src/validation/helpers.rs
Expand Up @@ -163,7 +163,8 @@ pub fn is_all_unique_and_sorted<'a, I: IntoIterator<Item = &'a T>, T: PartialOrd
true
}

/// This function checks that an input is a valid spendable UTXO
/// This function checks that an input is a valid spendable UTXO in the database. It cannot confirm
/// zero confermation transactions.
pub fn check_input_is_utxo<B: BlockchainBackend>(db: &B, input: &TransactionInput) -> Result<(), ValidationError> {
let output_hash = input.output_hash();
if let Some(utxo_hash) = db.fetch_unspent_output_hash_by_commitment(input.commitment()?)? {
Expand Down Expand Up @@ -203,7 +204,7 @@ pub fn check_input_is_utxo<B: BlockchainBackend>(db: &B, input: &TransactionInpu

warn!(
target: LOG_TARGET,
"Validation failed due to input: {} which does not exist yet", input
"Input ({}, {}) does not exist in the database yet", input.commitment()?.to_hex(), output_hash.to_hex()
);
Err(ValidationError::UnknownInput)
}
Expand Down

0 comments on commit 55077ce

Please sign in to comment.