Skip to content

Commit

Permalink
Add revalidation check to Output Manager service
Browse files Browse the repository at this point in the history
Outputs can become marked as invalid by the Output Manager service when they do not appear in the base nodes blockchain. Generally this means that they were re-orged out or have been spent by another copy of the wallet. However they can be reported as invalid incorrectly in some case like, for example, if the Base Node is not fully synced.

This PR adds in an addition check that the Output Manager does on startup that will see if any of the invalid outputs has become valid again. If it has that output will changed back into a spendable output.
  • Loading branch information
philipr-za committed Jun 5, 2020
1 parent 5294690 commit f0dec64
Show file tree
Hide file tree
Showing 8 changed files with 647 additions and 168 deletions.
2 changes: 2 additions & 0 deletions base_layer/wallet/src/output_manager_service/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ use std::time::Duration;
#[derive(Clone)]
pub struct OutputManagerServiceConfig {
pub base_node_query_timeout: Duration,
pub max_utxo_query_size: usize,
}

impl Default for OutputManagerServiceConfig {
fn default() -> Self {
Self {
base_node_query_timeout: Duration::from_secs(30),
max_utxo_query_size: 5000,
}
}
}
427 changes: 303 additions & 124 deletions base_layer/wallet/src/output_manager_service/service.rs

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions base_layer/wallet/src/output_manager_service/storage/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub trait OutputManagerBackend: Send + Sync {
/// If an unspent output is detected as invalid (i.e. not available on the blockchain) then it should be moved to
/// the invalid outputs collection. The function will return the last recorded TxId associated with this output.
fn invalidate_unspent_output(&self, output: &DbUnblindedOutput) -> Result<Option<TxId>, OutputManagerStorageError>;
/// If an invalid output is found to be valid this function will turn it back into an unspent output
fn revalidate_unspent_output(&self, spending_key: &BlindingFactor) -> Result<(), OutputManagerStorageError>;
}

/// Holds the outputs that have been selected for a given pending transaction waiting for confirmation
Expand Down Expand Up @@ -502,6 +504,14 @@ where T: OutputManagerBackend + 'static
.or_else(|err| Err(OutputManagerStorageError::BlockingTaskSpawnError(err.to_string())))
.and_then(|inner_result| inner_result)
}

pub async fn revalidate_output(&self, spending_key: BlindingFactor) -> Result<(), OutputManagerStorageError> {
let db_clone = self.db.clone();
tokio::task::spawn_blocking(move || db_clone.revalidate_unspent_output(&spending_key))
.await
.or_else(|err| Err(OutputManagerStorageError::BlockingTaskSpawnError(err.to_string())))
.and_then(|inner_result| inner_result)
}
}

fn unexpected_result<T>(req: DbKey, res: DbValue) -> Result<T, OutputManagerStorageError> {
Expand Down
17 changes: 17 additions & 0 deletions base_layer/wallet/src/output_manager_service/storage/memory_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use std::{
sync::{Arc, RwLock},
time::Duration,
};
use tari_core::transactions::types::BlindingFactor;

/// This structure is an In-Memory database backend that implements the `OutputManagerBackend` trait and provides all
/// the functionality required by the trait.
Expand Down Expand Up @@ -372,6 +373,22 @@ impl OutputManagerBackend for OutputManagerMemoryDatabase {
None => Err(OutputManagerStorageError::ValuesNotFound),
}
}

fn revalidate_unspent_output(&self, spending_key: &BlindingFactor) -> Result<(), OutputManagerStorageError> {
let mut db = acquire_write_lock!(self.db);
match db
.invalid_outputs
.iter()
.position(|v| v.output.unblinded_output.spending_key == *spending_key)
{
Some(pos) => {
let output = db.invalid_outputs.remove(pos);
db.unspent_outputs.push(output.clone());
Ok(())
},
None => Err(OutputManagerStorageError::ValuesNotFound),
}
}
}

// A struct that contains the extra info we are using in the Sql version of this backend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use tari_core::{
transactions::{
tari_amount::MicroTari,
transaction::{OutputFeatures, OutputFlags, UnblindedOutput},
types::{CryptoFactories, PrivateKey},
types::{BlindingFactor, CryptoFactories, PrivateKey},
},
};
use tari_crypto::tari_utilities::ByteArray;
Expand Down Expand Up @@ -429,6 +429,23 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase {

Ok(tx_id)
}

fn revalidate_unspent_output(&self, spending_key: &BlindingFactor) -> Result<(), OutputManagerStorageError> {
let conn = acquire_lock!(self.database_connection);
let output = OutputSql::find(&spending_key.to_vec(), &conn)?;

if OutputStatus::try_from(output.status)? != OutputStatus::Invalid {
return Err(OutputManagerStorageError::ValuesNotFound);
}
let _ = output.update(
UpdateOutput {
status: Some(OutputStatus::Unspent),
tx_id: None,
},
&(*conn),
)?;
Ok(())
}
}

/// A utility function to construct a PendingTransactionOutputs structure for a TxId, set of Outputs and a Timestamp
Expand Down
Loading

0 comments on commit f0dec64

Please sign in to comment.