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 dash-spv-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ clap = { version = "4.5", features = ["derive"] }

[dev-dependencies]
dash-spv = { path = "../dash-spv", features = ["test-utils"] }
# Tests inspect per-account transaction history end-to-end (including
# chainlocked transactions), which requires the `keep-finalized-transactions`
# feature on `key-wallet-ffi`. Cargo unifies features across the build graph at
# test time, so this enables the gated FFI accessors in test builds without
# changing the lib's default feature surface.
key-wallet-ffi = { path = "../key-wallet-ffi", features = ["keep-finalized-transactions"] }
serial_test = "3.0"
tempfile = "3.8"

Expand Down
10 changes: 10 additions & 0 deletions key-wallet-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ bip38 = ["key-wallet/bip38"]
bincode = ["key-wallet/bincode", "key-wallet-manager/bincode"]
eddsa = ["dashcore/eddsa", "key-wallet/eddsa"]
bls = ["dashcore/bls", "key-wallet/bls"]
# Forward to `key-wallet/keep-finalized-transactions` (via key-wallet-manager).
# With this on, every processed transaction (including chainlocked ones)
# stays in the in-memory `transactions` map for the wallet's lifetime.
# With it off (the default), records of chainlocked transactions are
# dropped and only their txids are kept (in `finalized_txids`) for dedup.
# See `key-wallet`'s feature documentation for details.
keep-finalized-transactions = [
"key-wallet/keep-finalized-transactions",
"key-wallet-manager/keep-finalized-transactions",
]

[dependencies]
key-wallet = { path = "../key-wallet" }
Expand Down
10 changes: 5 additions & 5 deletions key-wallet-ffi/FFI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,15 @@ Functions: 108
| `managed_account_collection_summary_data` | Get structured account collection summary data for managed collection ... | managed_account_collection |
| `managed_account_collection_summary_free` | Free a managed account collection summary and all its allocated memory #... | managed_account_collection |
| `managed_core_account_free` | Free a managed account handle # Safety - `account` must be a valid pointer... | managed_account |
| `managed_core_account_free_transactions` | Free transactions array returned by managed_core_account_get_transactions #... | managed_account |
| `managed_core_account_free_transactions` | Free transactions array returned by managed_core_account_get_transactions ... | managed_account |
| `managed_core_account_get_account_type` | Get the account type of a managed account # Safety - `account` must be a... | managed_account |
| `managed_core_account_get_address_pool` | Get an address pool from a managed account by type This function returns... | managed_account |
| `managed_core_account_get_balance` | Get the balance of a managed account # Safety - `account` must be a valid... | managed_account |
| `managed_core_account_get_external_address_pool` | Get the external address pool from a managed account This function returns... | managed_account |
| `managed_core_account_get_index` | Get the account index from a managed account Returns the primary account... | managed_account |
| `managed_core_account_get_internal_address_pool` | Get the internal address pool from a managed account This function returns... | managed_account |
| `managed_core_account_get_network` | Get the network of a managed account # Safety - `account` must be a valid... | managed_account |
| `managed_core_account_get_transaction_count` | Get the number of transactions in a managed account # Safety - `account`... | managed_account |
| `managed_core_account_get_transaction_count` | Get the number of transactions in a managed account Only available with the... | managed_account |
| `managed_core_account_get_transactions` | Get all transactions from a managed account Returns an array of... | managed_account |
| `managed_core_account_get_utxo_count` | Get the number of UTXOs in a managed account # Safety - `account` must be... | managed_account |
| `managed_platform_account_free` | Free a managed platform account handle # Safety - `account` must be a... | managed_account |
Expand Down Expand Up @@ -3047,7 +3047,7 @@ managed_core_account_free_transactions(transactions: *mut FFITransactionRecord,
```

**Description:**
Free transactions array returned by managed_core_account_get_transactions # Safety - `transactions` must be a pointer returned by `managed_core_account_get_transactions` - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation
Free transactions array returned by managed_core_account_get_transactions Only available with the `keep-finalized-transactions` Cargo feature, in which configuration `managed_core_account_get_transactions` is also available — the two functions are paired. # Safety - `transactions` must be a pointer returned by `managed_core_account_get_transactions` - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation

**Safety:**
- `transactions` must be a pointer returned by `managed_core_account_get_transactions` - `count` must be the count returned by `managed_core_account_get_transactions` - This function must only be called once per allocation
Expand Down Expand Up @@ -3175,7 +3175,7 @@ managed_core_account_get_transaction_count(account: *const FFIManagedCoreAccount
```

**Description:**
Get the number of transactions in a managed account # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance
Get the number of transactions in a managed account Only available with the `keep-finalized-transactions` Cargo feature. With the feature off (the default), records of chainlocked transactions are dropped from the in-memory map, so the count would not reflect the full history — the function is intentionally not exposed. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance

**Safety:**
- `account` must be a valid pointer to an FFIManagedCoreAccount instance
Expand All @@ -3191,7 +3191,7 @@ managed_core_account_get_transactions(account: *const FFIManagedCoreAccount, tra
```

**Description:**
Get all transactions from a managed account Returns an array of FFITransactionRecord structures. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_core_account_free_transactions`
Get all transactions from a managed account Returns an array of FFITransactionRecord structures. Only available with the `keep-finalized-transactions` Cargo feature. With the feature off (the default), records of chainlocked transactions are dropped from the in-memory map, so this would only return a partial history — the function is intentionally not exposed. # Safety - `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_core_account_free_transactions`

**Safety:**
- `account` must be a valid pointer to an FFIManagedCoreAccount instance - `transactions_out` must be a valid pointer to receive the transactions array pointer - `count_out` must be a valid pointer to receive the count - The caller must free the returned array using `managed_core_account_free_transactions`
Expand Down
48 changes: 40 additions & 8 deletions key-wallet-ffi/src/managed_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use dash_network::ffi::FFINetwork;
use dashcore::hashes::Hash;
use std::os::raw::{c_char, c_uint};
#[cfg(feature = "keep-finalized-transactions")]
use std::ptr::slice_from_raw_parts_mut;
use std::sync::Arc;

Expand Down Expand Up @@ -614,8 +615,14 @@ pub unsafe extern "C" fn managed_core_account_get_balance(
true
}

#[cfg(feature = "keep-finalized-transactions")]
/// Get the number of transactions in a managed account
///
/// Only available with the `keep-finalized-transactions` Cargo feature. With
/// the feature off (the default), records of chainlocked transactions are
/// dropped from the in-memory map, so the count would not reflect the full
/// history — the function is intentionally not exposed.
///
/// # Safety
///
/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance
Expand Down Expand Up @@ -914,10 +921,16 @@ impl Drop for FFITransactionRecord {
}
}

#[cfg(feature = "keep-finalized-transactions")]
/// Get all transactions from a managed account
///
/// Returns an array of FFITransactionRecord structures.
///
/// Only available with the `keep-finalized-transactions` Cargo feature. With
/// the feature off (the default), records of chainlocked transactions are
/// dropped from the in-memory map, so this would only return a partial
/// history — the function is intentionally not exposed.
///
/// # Safety
///
/// - `account` must be a valid pointer to an FFIManagedCoreAccount instance
Expand Down Expand Up @@ -951,8 +964,13 @@ pub unsafe extern "C" fn managed_core_account_get_transactions(
true
}

#[cfg(feature = "keep-finalized-transactions")]
/// Free transactions array returned by managed_core_account_get_transactions
///
/// Only available with the `keep-finalized-transactions` Cargo feature, in
/// which configuration `managed_core_account_get_transactions` is also
/// available — the two functions are paired.
///
/// # Safety
///
/// - `transactions` must be a pointer returned by `managed_core_account_get_transactions`
Expand Down Expand Up @@ -1547,10 +1565,13 @@ pub unsafe extern "C" fn managed_platform_account_result_free_error(
mod tests {
use super::*;
use crate::address_pool::address_pool_free;
use crate::types::{FFIAccountCreationOptionType, FFIWalletAccountCreationOptions};
// These types are only used by the FFITransactionRecord tests, which run
// only when transactions stay in memory.
#[cfg(feature = "keep-finalized-transactions")]
use crate::types::{
FFIAccountCreationOptionType, FFIBlockInfo, FFIInputDetail, FFIOutputDetail, FFIOutputRole,
FFITransactionContext, FFITransactionContextType, FFITransactionDirection,
FFITransactionType, FFIWalletAccountCreationOptions,
FFIBlockInfo, FFIInputDetail, FFIOutputDetail, FFIOutputRole, FFITransactionContext,
FFITransactionContextType, FFITransactionDirection, FFITransactionType,
};
use crate::wallet_manager::{
wallet_manager_add_wallet_from_mnemonic_with_options, wallet_manager_create,
Expand Down Expand Up @@ -1828,9 +1849,15 @@ mod tests {
assert_eq!(balance_out.locked, 0);
assert_eq!(balance_out.total, 0);

// Test get_transaction_count
let tx_count = managed_core_account_get_transaction_count(account);
assert_eq!(tx_count, 0); // Initially no transactions
// Test get_transaction_count (only available with the
// `keep-finalized-transactions` feature; without it the function
// is not exposed because chainlocked records are pruned and the
// count would be incomplete)
#[cfg(feature = "keep-finalized-transactions")]
{
let tx_count = managed_core_account_get_transaction_count(account);
assert_eq!(tx_count, 0); // Initially no transactions
}

// Test get_utxo_count
let utxo_count = managed_core_account_get_utxo_count(account);
Expand Down Expand Up @@ -1858,8 +1885,11 @@ mod tests {
let account_type = managed_core_account_get_account_type(ptr::null(), &mut index_out);
assert_eq!(account_type, FFIAccountKind::StandardBIP44); // Default type

let tx_count = managed_core_account_get_transaction_count(ptr::null());
assert_eq!(tx_count, 0);
#[cfg(feature = "keep-finalized-transactions")]
{
let tx_count = managed_core_account_get_transaction_count(ptr::null());
assert_eq!(tx_count, 0);
}

let utxo_count = managed_core_account_get_utxo_count(ptr::null());
assert_eq!(utxo_count, 0);
Expand Down Expand Up @@ -2080,6 +2110,7 @@ mod tests {
}
}

#[cfg(feature = "keep-finalized-transactions")]
#[test]
fn test_free_transactions_null_safety() {
unsafe {
Expand All @@ -2088,6 +2119,7 @@ mod tests {
}
}

#[cfg(feature = "keep-finalized-transactions")]
#[test]
fn test_ffi_transaction_record_roundtrip() {
let mut records = Vec::new();
Expand Down
7 changes: 7 additions & 0 deletions key-wallet-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ bincode = ["key-wallet/bincode", "dep:bincode"]
test-utils = ["key-wallet/test-utils"]
bls = ["key-wallet/bls"]
eddsa = ["key-wallet/eddsa"]
# Forward to `key-wallet/keep-finalized-transactions`. With this on,
# every processed transaction (including chainlocked ones) stays in
# the in-memory `transactions` map for the wallet's lifetime. With
# it off (the default), records of chainlocked transactions are
# dropped and only their txids are kept (in `finalized_txids`) for
# dedup. See `key-wallet`'s feature documentation for details.
keep-finalized-transactions = ["key-wallet/keep-finalized-transactions"]

[dependencies]
key-wallet = { path = "../key-wallet", default-features = false }
Expand Down
13 changes: 13 additions & 0 deletions key-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ bip38 = ["scrypt", "aes", "bs58", "rand"]
eddsa = ["dashcore/eddsa"]
bls = ["dashcore/bls"]
test-utils = ["dashcore/test-utils"]
# Keep the full `TransactionRecord` for transactions that have reached a
# "finalized in block" state — i.e. they have a ChainLock confirming the
# block they were mined in. With this feature ON, every processed
# transaction (including ones already chainlocked) stays in the
# `transactions` map for the wallet's lifetime; finalization is implicit
# in the stored `TransactionContext`. With it OFF (the default), records
# of chainlocked transactions are dropped from the map and a per-account
# `finalized_txids: HashSet<Txid>` retains only their txids so
# `has_transaction` / `transaction_is_finalized` still answer
# correctly. An InstantSend lock alone does NOT trigger record dropping
# — we keep the record around so the surrounding block confirmation can
# still write its height / block hash before the chainlock arrives.
keep-finalized-transactions = []

[dependencies]
internals = { path = "../internals", package = "dashcore-private" }
Expand Down
20 changes: 20 additions & 0 deletions key-wallet/src/managed_account/managed_account_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ pub trait ManagedAccountTrait {
/// Get mutable transactions
fn transactions_mut(&mut self) -> &mut BTreeMap<Txid, TransactionRecord>;

/// Returns `true` if this account has already processed `txid`,
/// whether it is still represented as a full record in `transactions`
/// or has been pruned (under the default feature configuration) and
/// is now only retained as a finalized-txid marker. Used by callers
/// that need to distinguish a brand-new sighting from a re-processing
/// (mempool → block, IS-lock arrival, chainlock, …).
fn has_transaction(&self, txid: &Txid) -> bool;

/// Returns `true` if `txid` has been mined in a block that is itself
/// chainlocked — the only finality signal we treat as terminal
/// (mirrors [`crate::transaction_checking::TransactionContext::is_chain_locked`]).
///
/// `InBlock` alone is not enough (the block can still be reorganized
/// out), and `InstantSend` alone is not enough either (the
/// surrounding block confirmation may still arrive and write the
/// height / block hash before the chainlock catches up). Only
/// `InChainLockedBlock` qualifies. This is the trigger for dropping
/// the full record under the default feature configuration.
fn transaction_is_finalized(&self, txid: &Txid) -> bool;

/// Return the current monitor revision.
///
/// Bumped whenever the monitored address set changes (e.g. new addresses
Expand Down
Loading
Loading