Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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_in_block` still answer
# correctly. An InstantSend lock alone does NOT trigger record dropping
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
# — 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