Skip to content
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
- name: Install cargo-machete
run: cargo install cargo-machete

- name: Install cargo-lints
run: cargo install cargo-lints

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

Expand All @@ -47,6 +50,9 @@ jobs:
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Run lints
run: cargo lints clippy --all-targets --all-features

- name: Build
run: cargo build --release --all-targets --all-features

Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/steps/payref_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// 5. Restart NodeA connected to NodeB so it adopts the longer chain (reorg)
// 6. Re-scan the wallet — the reorg handler saves old payrefs to payref_history
// 7. Start the daemon and verify the old payref still resolves via history fallback

#![allow(clippy::indexing_slicing)]
use cucumber::{given, then, when};
use std::net::TcpListener;
use std::process::{Command, Stdio};
Expand Down
14 changes: 14 additions & 0 deletions minotari/migrations/00031-add_maturity_to_outputs/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Track output maturity (the minimum block height at which the UTXO can be spent).
-- Coinbase outputs and time-locked outputs have a non-zero maturity; standard
-- outputs have maturity = 0 and are spendable as soon as they are confirmed.

ALTER TABLE outputs ADD COLUMN maturity INTEGER NOT NULL DEFAULT 0;

-- Backfill maturity for existing rows from the persisted wallet output JSON.
-- json_extract is part of SQLite's JSON1 extension which rusqlite enables by default.
-- Rows where extraction fails fall back to 0, which matches the column default.
UPDATE outputs
SET maturity = COALESCE(json_extract(wallet_output_json, '$.features.maturity'), 0)
WHERE wallet_output_json IS NOT NULL;

CREATE INDEX idx_outputs_maturity ON outputs(maturity);
25 changes: 17 additions & 8 deletions minotari/src/db/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use utoipa::ToSchema;

use crate::db::error::{WalletDbError, WalletDbResult};
use crate::db::outputs::get_output_totals_for_account;
use crate::db::scanned_tip_blocks::get_latest_scanned_tip_block_by_account;
use crate::utils::{
crypto::{decrypt_data, encrypt_data},
fingerprint::calculate_fingerprint,
Expand Down Expand Up @@ -273,6 +274,10 @@ pub struct AccountBalance {
/// The amount from incoming transactions that have not yet been confirmed.
#[schema(schema_with = micro_minotari_schema)]
pub unconfirmed: MicroMinotari,
/// The portion of the balance that is mined but still subject to an output
/// maturity (e.g. coinbase rewards) and therefore not yet spendable.
#[schema(schema_with = micro_minotari_schema)]
pub immature: MicroMinotari,
/// The total sum of all incoming (credit) transactions.
#[schema(schema_with = micro_minotari_schema)]
pub total_credits: Option<MicroMinotari>,
Expand All @@ -296,25 +301,29 @@ pub fn get_balance(conn: &Connection, account_id: i64) -> WalletDbResult<Account
"DB: Calculating account balance"
);
let history_agg = get_balance_aggregates_for_account(conn, account_id)?;
let (locked_amount, unconfirmed_amount, locked_and_unconfirmed_amount) =
get_output_totals_for_account(conn, account_id)?;
let tip_height = get_latest_scanned_tip_block_by_account(conn, account_id)?
.map(|b| b.height)
.unwrap_or(0);
let totals = get_output_totals_for_account(conn, account_id, tip_height)?;

let total_credits: MicroMinotari = (history_agg.total_credits.unwrap_or_default() as u64).into();
let total_debits: MicroMinotari = (history_agg.total_debits.unwrap_or_default() as u64).into();
let total_balance = total_credits.saturating_sub(total_debits);

let unavailable_balance = locked_amount
.saturating_add(unconfirmed_amount)
.saturating_sub(locked_and_unconfirmed_amount);
let available_balance = total_balance.saturating_sub(unavailable_balance);
// Derive `available` from the ledger so it stays in sync with `total` even when
// the `outputs` table and the balance-change ledger drift (e.g. during reorg
// recovery, or in tests that seed only one side). `totals.available` is the
// direct outputs-table view and is exposed separately on `OutputTotals`.
let available_balance = total_balance.saturating_sub(totals.unavailable);

let max_date_str = history_agg.max_date.map(format_timestamp);

Ok(AccountBalance {
total: total_balance,
available: available_balance,
locked: locked_amount,
unconfirmed: unconfirmed_amount,
locked: totals.locked,
unconfirmed: totals.unconfirmed,
immature: totals.immature,
total_credits: Some(total_credits),
total_debits: Some(total_debits),
max_height: history_agg.max_height,
Expand Down
6 changes: 4 additions & 2 deletions minotari/src/db/displayed_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,20 +365,22 @@ pub fn get_displayed_transactions_needing_confirmation_update(
) -> WalletDbResult<Vec<DisplayedTransaction>> {
let pending_status = format!("{:?}", TransactionDisplayStatus::Pending).to_lowercase();
let unconfirmed_status = format!("{:?}", TransactionDisplayStatus::Unconfirmed).to_lowercase();
let locked_status = format!("{:?}", TransactionDisplayStatus::Locked).to_lowercase();

let mut stmt = conn.prepare_cached(
r#"
SELECT transaction_json
FROM displayed_transactions
WHERE account_id = :account_id
AND status IN (:s1, :s2)
AND status IN (:s1, :s2, :s3)
"#,
)?;

let rows = stmt.query(named_params! {
":account_id": account_id,
":s1": pending_status,
":s2": unconfirmed_status
":s2": unconfirmed_status,
":s3": locked_status
})?;

process_json_rows(from_rows::<TransactionJsonRow>(rows))
Expand Down
8 changes: 4 additions & 4 deletions minotari/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ pub use scanned_tip_blocks::{

mod outputs;
pub use outputs::{
DbOutput, DbWalletOutput, fetch_outputs_by_lock_request_id, fetch_unspent_outputs, get_active_outputs_from_height,
get_output_by_id, get_output_info_by_hash, get_output_info_by_hash_for_account, get_output_totals_for_account,
get_total_unspent_balance, get_unconfirmed_outputs, insert_output, lock_output, mark_output_confirmed,
soft_delete_outputs_from_height, unlock_outputs_for_request,
DbOutput, DbWalletOutput, OutputTotals, fetch_outputs_by_lock_request_id, fetch_unspent_outputs,
get_active_outputs_from_height, get_output_by_id, get_output_info_by_hash, get_output_info_by_hash_for_account,
get_output_totals_for_account, get_total_unspent_balance, get_unconfirmed_outputs, insert_output, lock_output,
mark_output_confirmed, soft_delete_outputs_from_height, unlock_outputs_for_request,
unlock_outputs_for_request as unlock_outputs_for_pending_transaction, update_output_status,
};

Expand Down
Loading
Loading