Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
d686a3b
refactor(platform-wallet)!: hoist load history replay into shared load
lklimek Sep 30, 2026
bfc9b9f
feat(platform-wallet): add CORE_HISTORY_RESTORE persistence capability
lklimek Sep 30, 2026
ba6f6b1
feat(platform-wallet-ffi)!: restore recorded transaction history on load
lklimek Sep 30, 2026
c5787b7
feat(swift-sdk): supply recorded transaction history on wallet load
lklimek Sep 30, 2026
810a586
Merge commit 'fcfa46463a' into fix/wallet-load-history-replay
lklimek Sep 30, 2026
01220ce
Merge remote-tracking branch 'origin/fix/pr-5126' into fix/wallet-loa…
lklimek Oct 1, 2026
e298ed0
fix(wallet): rebuild spend guards for restored transaction records
lklimek Oct 1, 2026
04ed0f6
Merge branch 'fix/pr-5126' into fix/wallet-load-history-replay
lklimek Oct 2, 2026
d692cc7
Merge commit 'd397edb0fff52843e44e74f8e9e9cec7f3729385' into fix/wall…
lklimek Oct 12, 2026
4f03a59
fix(platform-wallet): settle stored conflicts before the load replay
lklimek Oct 12, 2026
d324442
fix(platform-wallet): keep asset-lock conflict evidence across the lo…
lklimek Oct 12, 2026
e255b50
docs(platform-wallet): explain both context upgrades of the load replay
lklimek Oct 12, 2026
f6f2004
chore: merge fix/pr-5126 into fix/wallet-load-history-replay
lklimek Oct 12, 2026
87febfb
chore: merge fix/pr-5126 into fix/wallet-load-history-replay
lklimek Oct 12, 2026
b13a1c1
fix(platform-wallet): plan load-replay sweeps only for contested spends
lklimek Oct 12, 2026
2a4368f
fix(platform-wallet): keep unsafe stored amounts away from the wallet…
lklimek Oct 12, 2026
d328a03
fix(platform-wallet): count a multi-account record as one spender
lklimek Oct 12, 2026
28254bc
fix(platform-wallet): spend a coin parked in a fallback account once
lklimek Oct 12, 2026
2c7d590
fix(platform-wallet): hand each planned sweep only the records it can…
lklimek Oct 12, 2026
24f56c6
fix(platform-wallet): do not re-dispatch sends the history replay ret…
lklimek Oct 12, 2026
fc08729
fix(platform-wallet): claim each reserved coin for one unsafe record …
lklimek Oct 12, 2026
6ecb3e2
fix(platform-wallet): keep the persisted state of coins the replay re…
lklimek Oct 12, 2026
f96d256
test(platform-wallet): build the replay test wallets from a fixed mne…
lklimek Oct 12, 2026
8f2264a
fix(platform-wallet): guard the inputs of settled spends whose fundin…
lklimek Oct 12, 2026
654044b
fix(platform-wallet)!: hold a reserved coin for the record that may s…
lklimek Oct 12, 2026
2aa1497
fix(platform-wallet): claim only the inputs of spends no fork can drop
lklimek Oct 12, 2026
f55739b
test(platform-wallet): pin the guard for an unconfirmed spend of a re…
lklimek Oct 12, 2026
35951bf
perf(platform-wallet): index pending asset-lock inputs before reading…
lklimek Oct 12, 2026
96fb93c
fix(platform-wallet): treat an overflow the recorded bodies prove as …
lklimek Oct 12, 2026
c95c984
fix(platform-wallet): claim the inputs of final asset locks on load
lklimek Oct 12, 2026
712ff9d
fix(platform-wallet): settle stored conflicts with a tracked asset lo…
lklimek Oct 12, 2026
8cf38ef
perf(platform-wallet): look tracked lock fundings up in the history's…
lklimek Oct 12, 2026
33f959f
fix(platform-wallet): do not claim the inputs of a tracked funding th…
lklimek Oct 12, 2026
9b7fb15
fix(platform-wallet): keep a chain-final lock's funding out of the hi…
lklimek Oct 12, 2026
be3a40e
fix(platform-wallet): withhold a chain-final funding unless the repla…
lklimek Oct 12, 2026
f5e9332
docs(platform-wallet): state the raw-record contract for chain-final …
lklimek Oct 12, 2026
89f9eae
fix(platform-wallet): keep the attached record of a chain-final fundi…
lklimek Oct 13, 2026
3e40057
fix(swift-sdk): reconcile SwiftData transaction accounting per wallet
lklimek Oct 13, 2026
4413d32
fix(swift-sdk): report a received amount despite a contact's watch-on…
lklimek Oct 13, 2026
7a282c9
fix(swift-sdk): scope linked outputs to the spending wallets in recon…
lklimek Oct 13, 2026
70a1b30
fix(swift-sdk): reconcile accounting when a TXO heal completes a spend
lklimek Oct 13, 2026
31910f3
chore: merge fix/pr-5126 into fix/wallet-load-history-replay
lklimek Oct 13, 2026
33c253b
fix(platform-wallet): reserve absent inputs of unsafe stored spends
lklimek Oct 20, 2026
0ad556a
chore: merge fix/pr-5126 into wallet history replay
lklimek Oct 20, 2026
bbb269c
chore: merge updated wallet base into Swift transaction accounting
lklimek Oct 21, 2026
018f00f
fix(swift-sdk): preserve base V4 schema before accounting migration
lklimek Oct 21, 2026
a22e324
style(swift-sdk): align migration stage indentation
lklimek Oct 21, 2026
3450b3a
chore(wallet): merge refreshed base into shared history replay
lklimek Oct 21, 2026
1f634bb
chore(platform-wallet): propagate reviewed SQLite capability fix
lklimek Oct 21, 2026
94f9833
chore(platform-wallet): propagate reviewed SQLite capability fix
lklimek Oct 21, 2026
1c058db
Merge branch 'fix/pr-5126' into fix/wallet-load-history-replay
lklimek Oct 27, 2026
9400fa3
Merge branch 'fix/pr-5126' into fix/swiftdata-transaction-accounting
lklimek Oct 27, 2026
accca0d
feat(platform-wallet): emit per-wallet transaction accounting to pers…
lklimek Oct 28, 2026
c081e87
feat(swift-sdk): persist Rust-emitted per-wallet transaction accounting
lklimek Oct 28, 2026
b59aa58
refactor(swift-sdk): drop the Swift-side transaction accounting deriv…
lklimek Oct 28, 2026
aa086b4
fix(swift-sdk): log the live schema version as the migration target
lklimek Oct 28, 2026
98db33e
fix(platform-wallet): make the accounting backfill converge and match…
lklimek Oct 28, 2026
651d1d5
fix(swift-sdk): mark per-wallet accounting backfills and stop scalar …
lklimek Oct 28, 2026
01ceb6c
fix(platform-wallet): leave a wallet spend with an unresolved coin un…
lklimek Oct 29, 2026
4b0879a
fix(platform-wallet-ffi): read accounting funding bodies strictly and…
lklimek Oct 29, 2026
1144d8b
fix(swift-sdk): fail the accounting round on a transaction-row read e…
lklimek Oct 29, 2026
c1d068b
docs(platform-wallet): state the P2PKH premise of the owned-input check
lklimek Oct 29, 2026
3a111c1
docs(swift-sdk): describe the open backfill in the V5 schema notes
lklimek Oct 29, 2026
f323849
fix(swift-sdk): report a failed tx-record fetch to Rust instead of a …
lklimek Oct 29, 2026
fa006bc
chore: merge fix/pr-5126 into PR 5417
lklimek Nov 5, 2026
c2780e9
chore: merge fix/pr-5126 into PR 5220
lklimek Nov 5, 2026
be655b8
refactor(wallet): integrate accounting-only storage into Swift history
lklimek Nov 5, 2026
ae9de38
refactor(wallet): establish shared replay and atomic SQLite adapter
lklimek Nov 5, 2026
ed6b62c
feat(wallet): retain deferred history snapshots and retry verified re…
lklimek Nov 5, 2026
e4fc727
feat(wallet): plan owner authorization repair for stored history
lklimek Nov 5, 2026
e389984
feat(wallet): apply owner authorization verdicts during atomic replay
lklimek Nov 5, 2026
7101253
fix(swift-sdk): expose accounting formatter and correct migration ass…
lklimek Nov 5, 2026
705d48e
Merge corrected Swift accounting parent into replay work
lklimek Nov 5, 2026
167fbf0
fix(wallet): fail history load when required finality evidence is mis…
lklimek Nov 5, 2026
12fec4e
test(wallet): authorize replay fixtures and verify finality evidence
lklimek Nov 5, 2026
1fd2316
fix(swift-sdk): start SPV before retrying persisted wallet history
lklimek Nov 5, 2026
03bac0b
refactor(wallet): prepare tracked history before atomic replay
lklimek Nov 5, 2026
144368d
feat(wallet-ffi): preserve raw InstantLock evidence across restore
lklimek Nov 5, 2026
de29628
fix(wallet-storage): validate transaction bodies during bulk history …
lklimek Nov 5, 2026
75f57d4
Merge branch 'fix/pr-5126' into fix/sqlite-bulk-history-identity
lklimek Nov 5, 2026
236867c
test(wallet-storage): preserve historical identities in bulk reads
lklimek Nov 5, 2026
20bc7a8
feat(wallet): preserve required finality evidence through history rescan
lklimek Nov 5, 2026
84baf52
test(wallet): define shared replay claim projection contract
lklimek Nov 5, 2026
1674bfc
feat(wallet-ffi): add atomic scoped history replay callbacks
lklimek Nov 5, 2026
c9550d2
feat(wallet): project replay verdicts onto durable input claims
lklimek Nov 5, 2026
6a65b58
refactor(wallet-storage): apply shared replay claim effects atomically
lklimek Nov 5, 2026
5c176cf
feat(platform-wallet): apply shared replay effects in host transactions
lklimek Nov 5, 2026
79f4bd0
fix(platform-wallet): gate every active history source through replay
lklimek Nov 5, 2026
a27c681
fix(platform-wallet): preserve replay projection and migration regres…
lklimek Nov 5, 2026
59dc06d
fix(wallet-storage)!: reconcile historical AssetUnlock identities
lklimek Nov 5, 2026
3e2e29d
test(platform-wallet): cover unverified SPV lock persistence boundary
lklimek Nov 5, 2026
b7bcbdc
test(wallet): reproduce stale consumed asset-lock spendability
lklimek Nov 5, 2026
2876cf4
merge(wallet-storage): inherit bulk history identity validation
lklimek Nov 5, 2026
0a7de05
fix(platform-wallet): durably withhold finalized asset-lock inputs
lklimek Nov 5, 2026
9e06bae
test(wallet-storage): require verified evidence for restored InstantSend
lklimek Nov 5, 2026
0061164
test(swift-sdk): account for history read before asset-lock failure
lklimek Nov 5, 2026
1ea9eab
chore(swift-sdk): merge validated history prerequisites into accounting
lklimek Nov 5, 2026
c95c0c0
chore: merge fix/pr-5126 into PR 5781
lklimek Nov 6, 2026
2ca1217
chore: merge fix/sqlite-bulk-history-identity into PR 5783
lklimek Nov 6, 2026
268a84f
fix(platform-wallet-storage): drop Hash trait imports unused after th…
lklimek Nov 6, 2026
39a3319
chore: merge fix/sqlite-asset-unlock-history-aliases into PR 5417
lklimek Nov 6, 2026
28a6650
fix(platform-wallet): follow the rust-dashcore hash API in transactio…
lklimek Nov 6, 2026
5d8e34a
chore: merge fix/swiftdata-transaction-accounting into PR 5220
lklimek Nov 6, 2026
d8bbf50
test(swift-sdk): assert migrations land on the current schema version
lklimek Nov 6, 2026
15c3385
fix(wallet): release persisted wallets when their manager is dropped
lklimek Nov 6, 2026
e76b949
fix(wallet): preserve history and surviving input claims across replay
lklimek Nov 6, 2026
3c59183
test(wallet-storage): pin rescan boundaries and atomic load exclusion
lklimek Nov 6, 2026
3dfee04
test(wallet): model proof discovery and atomic replay callback requir…
lklimek Nov 6, 2026
b6ee44b
chore: merge fix/swiftdata-transaction-accounting into PR 5220
lklimek Nov 6, 2026
dd21652
fix(platform-wallet): adapt replay evidence to current SPV APIs
lklimek Nov 6, 2026
ecc4921
test(swift-sdk): account for the InstantLock entity in the V5 schema
lklimek Nov 6, 2026
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
30 changes: 18 additions & 12 deletions Cargo.lock

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

17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "9b19035f5
grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "9b19035f51905b156e8a628c10b71b85797ffa21" }
grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "9b19035f51905b156e8a628c10b71b85797ffa21" }
grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "9b19035f51905b156e8a628c10b71b85797ffa21" }
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
dashcore-crypto = { git = "https://github.com/dashpay/rust-dashcore", rev = "c9aeac0d0dcbd4b91d780a70d61b9bb693199ca4" }
# TODO(upstream-merge): move this wallet-restore authorization pin to merged rust-dashcore dev after PR #1336 lands.
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
dashcore-crypto = { git = "https://github.com/dashpay/rust-dashcore", rev = "6f206d482d38def57b866f9dae1a6e98f84d25a9" }
tokio-metrics = "0.5"

# Size-tuned profile for the iOS `rs-unified-sdk-ffi` staticlib, which
Expand Down
3 changes: 3 additions & 0 deletions packages/rs-platform-wallet-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ hex = "0.4"
# Persistence loader emits structured warnings for skipped /
# corrupt rows so operators can detect snapshot drift without a
# native debugger attached.
thiserror = "=2.0.18"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }

Expand All @@ -64,6 +65,8 @@ bs58 = "0.5"
zeroize = { version = "1", features = ["derive"] }

[dev-dependencies]
dash-spv = { workspace = true, features = ["test-utils"] }
key-wallet-manager = { workspace = true, features = ["test-utils"] }
dpp = { path = "../rs-dpp", features = ["fixtures-and-mocks"] }
platform-wallet = { path = "../rs-platform-wallet", features = ["test-utils"] }

Expand Down
83 changes: 68 additions & 15 deletions packages/rs-platform-wallet-ffi/src/core_wallet_types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! C-compatible types for core wallet changeset FFI.

use key_wallet::managed_account::transaction_record::TransactionDirection;
use platform_wallet::changeset::WalletAccounting;
use platform_wallet::masternode::{provider_payload_fields, MasternodeRecord};
use std::os::raw::c_char;

Expand Down Expand Up @@ -363,6 +365,38 @@ pub struct AccountChangeSetFFI {
pub has_internal_highest_used: bool,
}

/// One transaction's Core accounting from ONE wallet's point of view, as
/// `platform_wallet::changeset::WalletAccounting` computes it. The host
/// persists it keyed by `(txid, wallet_id)` (the wallet is the callback's
/// `wallet_id`) and displays it; it never derives or corrects it.
///
/// [`TransactionRecordFFI`]'s own `net_amount` / `direction` / `fee` stay
/// for hosts that predate this row. A record with no accounting evidence
/// yields NO row here, so the host keeps what it stored.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransactionAccountingFFI {
pub txid: [u8; 32],
/// `Σ owned outputs − Σ owned inputs` in duffs (positive = received).
pub net_amount: i64,
/// 0=incoming, 1=outgoing, 2=internal, 3=coinJoin.
pub direction: u32,
/// Meaningful only when `has_fee`.
pub fee: u64,
pub has_fee: bool,
}

/// One stored Core transaction without this wallet's accounting row, handed
/// back for the load-time backfill (see
/// `on_load_core_history_for_accounting_fn`). Host-owned until the matching
/// free callback.
#[repr(C)]
pub struct CoreHistoryTransactionFFI {
/// Consensus-encoded transaction body.
pub tx_bytes: *const u8,
pub tx_bytes_len: usize,
}

// ---------------------------------------------------------------------------
// Top-level wallet changeset
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -487,15 +521,8 @@ impl WalletChangeSetFFI {
/// snapshots delivered through the `BalanceUpdateHandler`, not as
/// a delta on the persistence path.
///
/// TODO(events): wire `instant_locks_for_non_final_records` through
/// the FFI surface. Today the Swift side learns about IS-lock state
/// only when a re-emitted `TransactionRecord` flows through `records`
/// with `context = InstantSend(..)`. The standalone IS-lock map is
/// dropped here. Acceptable as long as the event adapter re-emits
/// affected records (it does for `TransactionDetected`,
/// `BlockProcessed` and `TransactionInstantLocked`).
/// When the standalone IS-lock event needs to flow to Swift, add
/// a `BTreeMap`-shaped FFI field here and populate it.
/// Raw InstantLocks ride the size-gated persistence extension callback;
/// they do not change this legacy struct's allocation or array stride.
pub fn from_changeset(cs: &platform_wallet::changeset::CoreChangeSet) -> Self {
use key_wallet::account::AccountType;
use std::ffi::CString;
Expand Down Expand Up @@ -1523,6 +1550,37 @@ pub(crate) fn masternode_entry_v2_ffi(
}
}

/// `direction` wire values of [`TransactionRecordFFI`] and
/// [`TransactionAccountingFFI`], exported so hosts name them from the
/// generated header instead of re-declaring them.
pub const PLATFORM_WALLET_TRANSACTION_DIRECTION_INCOMING: u32 = 0;
pub const PLATFORM_WALLET_TRANSACTION_DIRECTION_OUTGOING: u32 = 1;
pub const PLATFORM_WALLET_TRANSACTION_DIRECTION_INTERNAL: u32 = 2;
pub const PLATFORM_WALLET_TRANSACTION_DIRECTION_COIN_JOIN: u32 = 3;

fn direction_to_u32(direction: TransactionDirection) -> u32 {
match direction {
TransactionDirection::Incoming => PLATFORM_WALLET_TRANSACTION_DIRECTION_INCOMING,
TransactionDirection::Outgoing => PLATFORM_WALLET_TRANSACTION_DIRECTION_OUTGOING,
TransactionDirection::Internal => PLATFORM_WALLET_TRANSACTION_DIRECTION_INTERNAL,
TransactionDirection::CoinJoin => PLATFORM_WALLET_TRANSACTION_DIRECTION_COIN_JOIN,
}
}

impl From<&WalletAccounting> for TransactionAccountingFFI {
fn from(row: &WalletAccounting) -> Self {
let mut txid = [0u8; 32];
txid.copy_from_slice(row.txid.as_ref());
Self {
txid,
net_amount: row.net_amount,
direction: direction_to_u32(row.direction),
fee: row.fee.unwrap_or(0),
has_fee: row.fee.is_some(),
}
}
}

fn tx_record_to_ffi(
tr: &key_wallet::managed_account::transaction_record::TransactionRecord,
) -> TransactionRecordFFI {
Expand Down Expand Up @@ -1569,12 +1627,7 @@ fn tx_record_to_ffi(
}
};

let dir_val = match tr.direction {
key_wallet::managed_account::transaction_record::TransactionDirection::Incoming => 0u32,
key_wallet::managed_account::transaction_record::TransactionDirection::Outgoing => 1,
key_wallet::managed_account::transaction_record::TransactionDirection::Internal => 2,
key_wallet::managed_account::transaction_record::TransactionDirection::CoinJoin => 3,
};
let dir_val = direction_to_u32(tr.direction);

let type_str = CString::new(format!("{:?}", tr.transaction_type))
.unwrap_or_else(|_| CString::new("Unknown").unwrap());
Expand Down
Loading
Loading