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

Fail transaction logs when input txos are consumed #1028

Merged
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
87 changes: 52 additions & 35 deletions full-service/src/db/transaction_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ use crate::{
Account, NewTransactionInputTxo, NewTransactionLog, TransactionInputTxo,
TransactionLog, TransactionOutputTxo, Txo,
},
schema::{
transaction_input_txos, transaction_logs,
transaction_logs::dsl::{id as dsl_id, transaction_logs as dsl_transaction_logs},
transaction_output_txos, txos,
},
txo::{TxoID, TxoModel},
Conn, WalletDbError,
},
Expand Down Expand Up @@ -322,24 +327,38 @@ pub trait TransactionLogModel {
conn: Conn
) -> Result<(), WalletDbError>;

/// Update the finalized block index to all pending transaction logs that associate with a given transaction output (txo).
/// Update the finalized block index to all pending transaction logs that have an output
/// transaction corresponding to `transaction_output_txo_id_hex`.
///
/// # Arguments
///
///| Name | Purpose | Notes |
///|-------------------------|------------------------------------------------------------------------------|-------|
///| `txo_id_hex` | The txo ID for which to get all transaction logs associated with this txo ID.| |
///| `finalized_block_index` | The block index at which the transaction will be completed and finalized. | |
///| `conn` | An reference to the pool connection of wallet database | |
///
/// # Returns
/// * unit
/// * `transaction_output_txo_id_hex` - The txo ID for which to get all transaction logs
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved
/// associated with this txo ID.
/// * `finalized_block_index` - The block index at which the transaction will be completed and
/// finalized.
/// * `conn` - A reference to the pool connection of wallet database
fn update_pending_associated_with_txo_to_succeeded(
txo_id_hex: &str,
transaction_output_txo_id_hex: &str,
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved
finalized_block_index: u64,
conn: Conn,
) -> Result<(), WalletDbError>;


/// Update all transaction logs that have an input transaction corresponding to
/// `transaction_input_txo_id_hex` to failed.
///
/// Note: When processing inputs and outputs from the same block, be sure to mark the
/// appropriate transaction logs as succeeded prior to calling this method. See
/// `update_pending_associated_with_txo_to_succeeded()`.
///
/// # Arguments
/// * `transaction_input_txo_id_hex` - The txo ID for which to get all transaction logs
/// associated with this txo ID.
/// * `conn` - A reference to the pool connection of wallet database
fn update_consumed_txo_to_failed(
transaction_input_txo_id_hex: &str,
conn: Conn,
) -> Result<(), WalletDbError>;

/// Set the status of a transaction log to failed if its tombstone_block_index is less than the given block index.
///
/// # Arguments
Expand Down Expand Up @@ -407,9 +426,7 @@ impl TransactionLogModel for TransactionLog {
}

fn get(id: &TransactionId, conn: Conn) -> Result<TransactionLog, WalletDbError> {
use crate::db::schema::transaction_logs::dsl::{id as dsl_id, transaction_logs};
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved

match transaction_logs
match dsl_transaction_logs
.filter(dsl_id.eq(id.to_string()))
.get_result::<TransactionLog>(conn)
{
Expand All @@ -423,8 +440,6 @@ impl TransactionLogModel for TransactionLog {
}

fn get_associated_txos(&self, conn: Conn) -> Result<AssociatedTxos, WalletDbError> {
use crate::db::schema::{transaction_input_txos, transaction_output_txos, txos};

let inputs: Vec<Txo> = txos::table
.inner_join(transaction_input_txos::table)
.filter(transaction_input_txos::transaction_log_id.eq(&self.id))
Expand Down Expand Up @@ -463,8 +478,6 @@ impl TransactionLogModel for TransactionLog {
submitted_block_index: u64,
conn: Conn,
) -> Result<(), WalletDbError> {
use crate::db::schema::transaction_logs;

diesel::update(self)
.set(transaction_logs::submitted_block_index.eq(Some(submitted_block_index as i64)))
.execute(conn)?;
Expand All @@ -473,8 +486,6 @@ impl TransactionLogModel for TransactionLog {
}

fn update_comment(&self, comment: String, conn: Conn) -> Result<(), WalletDbError> {
use crate::db::schema::transaction_logs;

diesel::update(self)
.set(transaction_logs::comment.eq(comment))
.execute(conn)?;
Expand All @@ -488,8 +499,6 @@ impl TransactionLogModel for TransactionLog {
tombstone_block_index: Option<i64>,
conn: Conn,
) -> Result<(), WalletDbError> {
use crate::db::schema::transaction_logs;

diesel::update(self)
.set((
transaction_logs::tx.eq(tx),
Expand All @@ -507,8 +516,6 @@ impl TransactionLogModel for TransactionLog {
max_block_index: Option<u64>,
conn: Conn,
) -> Result<Vec<(TransactionLog, AssociatedTxos, ValueMap)>, WalletDbError> {
use crate::db::schema::transaction_logs;

let mut query = transaction_logs::table.into_boxed();

if let Some(account_id) = account_id {
Expand Down Expand Up @@ -550,7 +557,6 @@ impl TransactionLogModel for TransactionLog {
account_id: &AccountID,
conn: Conn,
) -> Result<TransactionLog, WalletDbError> {
use crate::db::schema::{transaction_input_txos, transaction_logs};
// Verify that the account exists.
Account::get(account_id, conn)?;

Expand Down Expand Up @@ -744,10 +750,6 @@ impl TransactionLogModel for TransactionLog {
}

fn delete_all_for_account(account_id_hex: &str, conn: Conn) -> Result<(), WalletDbError> {
use crate::db::schema::{
transaction_input_txos, transaction_logs, transaction_output_txos,
};

let transaction_input_txos: Vec<TransactionInputTxo> = transaction_input_txos::table
.inner_join(transaction_logs::table)
.filter(transaction_logs::account_id.eq(account_id_hex))
Expand Down Expand Up @@ -781,7 +783,6 @@ impl TransactionLogModel for TransactionLog {
finalized_block_index: u64,
conn: Conn,
) -> Result<(), WalletDbError> {
use crate::db::schema::{transaction_logs, transaction_output_txos};
// Find all submitted transaction logs associated with this txo that have not
// yet been finalized (there should only ever be one).
let transaction_log_ids: Vec<String> = transaction_logs::table
Expand All @@ -802,13 +803,32 @@ impl TransactionLogModel for TransactionLog {
Ok(())
}

fn update_consumed_txo_to_failed(
transaction_input_txo_id_hex: &str,
conn: Conn,
) -> Result<(), WalletDbError> {
let transaction_log_ids: Vec<String> = transaction_logs::table
.inner_join(transaction_input_txos::table)
.filter(transaction_input_txos::txo_id.eq(transaction_input_txo_id_hex))
nick-mobilecoin marked this conversation as resolved.
Show resolved Hide resolved
.filter(transaction_logs::failed.eq(false))
.filter(transaction_logs::finalized_block_index.is_null())
.select(transaction_logs::id)
.load(conn)?;

diesel::update(
transaction_logs::table.filter(transaction_logs::id.eq_any(transaction_log_ids)),
)
.set((transaction_logs::failed.eq(true),))
.execute(conn)?;

Ok(())
}

fn update_pending_exceeding_tombstone_block_index_to_failed(
account_id: &AccountID,
block_index: u64,
conn: Conn,
) -> Result<(), WalletDbError> {
use crate::db::schema::transaction_logs;

diesel::update(
transaction_logs::table
.filter(transaction_logs::account_id.eq(&account_id.0))
Expand Down Expand Up @@ -1169,9 +1189,6 @@ mod tests {

#[async_test_with_logger]
async fn test_delete_transaction_logs_for_account(logger: Logger) {
use crate::db::schema::{
transaction_input_txos, transaction_logs, transaction_output_txos,
};
use diesel::dsl::count_star;

let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -827,10 +827,9 @@ mod e2e_transaction {

// The first transaction log will have succeeded, because it's outputs
// show up on the ledger.
// The second transaction log will still be pending, because it's outputs
// don't exist in the ledger. This second transaction log will
// eventually fail once the tombstone block is reached.
let expected_statuses = [TxStatus::Succeeded, TxStatus::Pending];
// The second transaction log will still be failed, because it's inputs
// were consumed but its outputs don't exist
let expected_statuses = [TxStatus::Succeeded, TxStatus::Failed];

tx_log_id_and_proposals
.iter()
Expand Down
4 changes: 4 additions & 0 deletions full-service/src/service/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ pub fn sync_account_next_chunk(

for (block_index, txo_id_hex) in &spent_txos {
Txo::update_spent_block_index(txo_id_hex, *block_index, conn)?;
// NB: This needs to be done after calling
// `TransactionLog::update_pending_associated_with_txo_to_succeeded()` so we
// don't fail a transaction log that is finalized for this block.
TransactionLog::update_consumed_txo_to_failed(txo_id_hex, conn)?;
}

TransactionLog::update_pending_exceeding_tombstone_block_index_to_failed(
Expand Down