Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b3904ee
fix(wallet): use upstream late transaction accounting corrections
lklimek Sep 28, 2026
a72737a
fix(swift-sdk): reconcile persisted Core transaction accounting
lklimek Sep 28, 2026
c4a3be4
fix(swift-sdk): exclude contact TXOs from history accounting
lklimek Sep 28, 2026
60e1f6d
fix(wallet): reconcile persisted Core transaction accounting
lklimek Sep 28, 2026
82cec94
refactor(wallet): pin minimal Core transaction accounting fix
lklimek Sep 28, 2026
37ea1bb
Merge branch 'v4.3-dev' into fix/pr-5126
lklimek Sep 29, 2026
e80b55e
Merge remote-tracking branch 'origin/v4.3-dev' into fix/pr-5126
lklimek Sep 29, 2026
c0425f7
fix(platform-wallet-storage): restore confirmed spends before wallet …
lklimek Sep 29, 2026
9c8dff2
fix(platform-wallet-storage): keep each replayed UTXO in one funds ac…
lklimek Sep 29, 2026
4b80513
fix(platform-wallet-storage): never load contact-only outputs as spen…
lklimek Sep 29, 2026
3d04bb0
fix(swift-sdk): skip load-time accounting repair inside an open chang…
lklimek Sep 29, 2026
55730ff
Merge remote-tracking branch 'origin/v4.3-dev' into fix/pr-5126
lklimek Sep 30, 2026
9766169
fix(platform-version): select folded DRIVE_ABCI_QUERY_VERSIONS_V2 in …
lklimek Sep 30, 2026
28796ac
fix(platform-wallet): drop unused IdentityGettersV0 import in dpns
lklimek Sep 30, 2026
c2a5765
test(drive): regenerate GroveDB structure for protocol version 15
lklimek Sep 30, 2026
c4a5b23
fix(platform-wallet-storage)!: harden wallet history restore after #5…
lklimek Sep 30, 2026
1279864
fix(platform-wallet-storage): gate V019 blob length before reading it…
lklimek Sep 30, 2026
b4b7366
fix(platform-wallet): let the higher-ranked record win account coales…
lklimek Sep 30, 2026
fcfa464
test(swift-sdk): expect the load-time accounting reads before the loc…
lklimek Sep 30, 2026
b004bc9
fix(swift-sdk): keep incoming amounts when a payment has foreign inputs
lklimek Sep 30, 2026
1c8ec82
test(swift-sdk): cover wallet-scoped direction and amounts
lklimek Sep 30, 2026
a70ee60
fix(platform-wallet-storage): validate birth height before scheduling…
lklimek Sep 30, 2026
c0f3a4c
fix(platform-wallet): classify asset locks as internal on the live path
lklimek Sep 30, 2026
6e82bf1
test(platform-wallet): pin live-fold accounting for non-asset-lock tr…
lklimek Sep 30, 2026
7002582
docs(swift-sdk): point the direction rule comment at wallet_direction
lklimek Sep 30, 2026
5e74f42
Merge branch 'v5.1-dev' into fix/pr-5126
lklimek Oct 1, 2026
b1610d3
fix(platform-wallet-storage): ignore witness-only body differences fo…
lklimek Oct 1, 2026
a4e1c3f
refactor(platform-wallet): share the net-amount formula with the SQLi…
lklimek Oct 1, 2026
be7f8ec
fix(swift-sdk): read account wallets through the Optional cast in acc…
lklimek Oct 1, 2026
628efc3
test(drive): relabel team-action layers in grovedb-structure.json for…
lklimek Oct 1, 2026
f78bccb
perf(platform-wallet): index accounted outputs once in wallet accounting
lklimek Oct 1, 2026
3eff6f6
Merge branch 'v5.1-dev' into fix/pr-5126
lklimek Oct 2, 2026
aec5bb8
Merge remote-tracking branch 'origin/fix/structure-tests-latest-proto…
lklimek Oct 2, 2026
b042573
Merge remote-tracking branch 'origin/fix/structure-tests-latest-proto…
lklimek Oct 2, 2026
defafa2
fix(platform-wallet-storage): rebuild spend marks for height-only fun…
lklimek Oct 2, 2026
0f09a3a
fix(platform-wallet-storage): settle InstantSend conflicts regardless…
lklimek Oct 2, 2026
bd49cfe
fix(platform-wallet-storage): gate owned-output script length before …
lklimek Oct 2, 2026
d82b65e
docs(swift-sdk): mark shared-wallet amount gap for Rust-sourced accou…
lklimek Oct 2, 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
24 changes: 12 additions & 12 deletions Cargo.lock

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

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "dce8252f8
grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "dce8252f8665bf06c7bd788e739efba354141690" }
grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "dce8252f8665bf06c7bd788e739efba354141690" }
grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "dce8252f8665bf06c7bd788e739efba354141690" }
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "e4208c90786a6854bd498315bcb571ef24182c15" }
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "18f7f3e695e770ea5d2820aa85597d45160d1b8e" }
tokio-metrics = "0.5"

# Size-tuned profile for the iOS `rs-unified-sdk-ffi` staticlib, which
Expand Down
49 changes: 44 additions & 5 deletions packages/rs-platform-wallet-storage/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Any `meta_*` row whose parent object does not exist — because it was never cre

A future garbage-collection pass is expected to reap orphan metadata — rows with no live parent object older than approximately one week — but no such GC is implemented yet. Callers should not rely on orphan metadata persisting forever, nor assume it will be cleaned up promptly. `meta_global` is intentionally parentless and always survives.

The tables are split into five domain diagrams below. `WALLETS` is the root anchor and appears in each diagram. The diagrams cover 21 of V001's 23 tables; `pending_contact_crypto` and `ignored_senders` appear in the [Tables](#tables) section but are not diagrammed. They show the V001 tables as amended in place by every later migration that changes one of them (the current `core_utxos`, `core_transactions`, `platform_addresses`, and `asset_locks` shapes). Nine tables added by later migrations are not yet diagrammed here: `core_address_pool`, `meta_data_versions`, and `meta_store_generation` (V009, plus its V010–V011 `core_address_pool` columns), `invitations` (V003), `shielded_viewing_keys` (V013), `dpns_name_states` (V005), `tracked_masternodes` (V006), and the `identity_scan_states` / `identity_scan_failed_indices` pair (V017) — see the [Migrations](#migrations) log for what each adds in the meantime.
The tables are split into five domain diagrams below. `WALLETS` is the root anchor and appears in each diagram. The diagrams cover 21 of V001's 23 tables; `pending_contact_crypto` and `ignored_senders` appear in the [Tables](#tables) section but are not diagrammed. They show the V001 tables as amended in place by every later migration that changes one of them (the current `core_utxos`, `core_transactions`, `platform_addresses`, and `asset_locks` shapes). Eleven tables added by later migrations are not yet diagrammed here: `core_address_pool`, `meta_data_versions`, and `meta_store_generation` (V009, plus its V010–V011 `core_address_pool` columns), `invitations` (V003), `shielded_viewing_keys` (V013), `dpns_name_states` (V005), `tracked_masternodes` (V006), the `identity_scan_states` / `identity_scan_failed_indices` pair (V017), and the `core_transaction_inputs` / `core_transaction_record_originals` pair (V019) — see the [Migrations](#migrations) log for what each adds in the meantime.

## Diagram 1 — Core / L1 (Bitcoin/Dash layer)

Expand Down Expand Up @@ -89,7 +89,7 @@ erDiagram
BLOB script "scriptPubKey bytes"
INTEGER is_sweep_placeholder "1 until funding arrives"
INTEGER spent "0 | 1"
BLOB spent_in_txid "set by apply_sweep for an unresolved held input; else NULL"
BLOB spent_in_txid "spender claiming the row: apply_sweep hold, runtime or V019 history repair; else NULL"
INTEGER winner_mined_height "V007: sweep winner's mined height; NULL when unstamped or materialised"
}

Expand Down Expand Up @@ -402,9 +402,20 @@ One row per UTXO, spent or unspent. Owning-account identity is derived from
`core_address_pool` while loading wallet state, and confirmation height is
derived from `core_transactions.height`.

`spent_in_txid` is written only by
`apply_sweep`, naming the winner that took an input a swept loser claimed
but this store had no released record for. It is set to NULL by a trigger
`spent_in_txid` names the transaction that claims the row. Three writers set
it:

- `apply_sweep`, naming the winner that took an input a swept loser claimed
but this store had no released record for;
- the runtime history repair (`schema::core_history`), which marks an owned
output `spent = 1` for each non-mempool stored record that spends it,
including a record stored before the output itself was known;
- the V019 migration repair (`migrations::legacy_v019`), which does the same
once over every stored record.

Both repairs only fill the link: an existing claim (`spent = 1` with a
non-NULL `spent_in_txid`) stands, so a repair never overwrites another
spender. It is set to NULL by a trigger
when its referenced `core_transactions` row is deleted (instead of a native
`ON DELETE SET NULL`, which would also null the NOT NULL `wallet_id`
column) — and by a later sweep that releases the same outpoint.
Expand Down Expand Up @@ -440,6 +451,33 @@ spent and is permanently outside the collector's reach.
the collector's per-round scan touches tombstones rather than the
wallet's full spent history.

### `core_transaction_inputs`

Raw-input index (V019): one row per input outpoint of every stored transaction
record, written whether or not the store knows the spent output yet. When an
owned output is recorded later, the runtime history repair looks up its
outpoint here to find the stored records that spend it and repairs their
accounting (`input_details`, direction, the output's spent mark). Rows are
written with `INSERT OR IGNORE` on each record write; the V019 migration
backfilled them for records stored before it.

- PK: `(wallet_id, txid, outpoint)`.
- FK: `(wallet_id, txid) → core_transactions(wallet_id, txid) ON DELETE
CASCADE`.
- Index: `idx_core_transaction_inputs_outpoint(wallet_id, outpoint)`.

### `core_transaction_record_originals`

Append-only archive (V019) of the pre-repair `core_transactions.record_blob`.
Before a history repair (runtime or V019) first rewrites a record, the blob it
replaces is copied here with `INSERT OR IGNORE`, so the first original is kept
verbatim and never replaced. It is never loaded; it exists so a wrong repair
can be undone by hand.

- PK: `(wallet_id, txid)`.
- FK: `wallet_id → wallets(wallet_id) ON DELETE CASCADE`. There is no FK to
`core_transactions`: the original outlives its transaction row.

### `core_instant_locks`

Instant-lock blobs for transactions that are broadcast but not yet
Expand Down Expand Up @@ -847,3 +885,4 @@ table-rebuild migration, as V004 does.
| V016 | `V016__identity_keys_null_scope_requires_existing_identity.rs` | Recreates the `identity_keys` null-scope trigger pair (see Triggers above) to also reject a NULL-scoped key naming an identity that does not exist at all, closing the gap where V008's guard caught only the wallet-owned case. |
| V017 | `V017__identity_scan_state.rs` | Adds `identity_scan_states` (one row per wallet: the last gap-limit identity-scan verdict — `complete`, `probed_from`/`probed_through`, `unlocated_gap`) and `identity_scan_failed_indices` (indices probed without an answer, cascading from the verdict row via `wallet_id`). Purely additive; an upgraded database reads back "no verdict recorded" for every wallet until the next scan (dashpay/platform#4365). |
| V018 | `V018__identity_hard_delete.rs` | Retires identity tombstoning. Adds `cascade_children_on_identity_delete` (brooms `identity_keys` / `contacts` / `ignored_senders` / `pending_contact_crypto` by the deleted identity id, covering the rows no live FK reaches) plus its access-path indexes `idx_contacts_owner`, `idx_ignored_senders_owner`, and `idx_pending_contact_crypto_owner`; purges every already-tombstoned identity and its dependents; drops `identities.tombstoned`. |
| V019 | `V019__core_transaction_accounting.rs` | Adds `core_transaction_inputs` (raw-input index, with `idx_core_transaction_inputs_outpoint`) and `core_transaction_record_originals` (append-only archive of pre-repair record blobs). A data repair (`legacy_v019::repair_history`) then runs once over every stored record: it backfills the input index, rewrites `core_transactions.record_blob` with corrected input details and direction (archiving the original first), and marks spent the owned outputs that non-mempool records spend, recording the spender in `spent_in_txid` unless another claim stands. A confirmed record whose blob cannot be decoded (or exceeds the blob size limit) is dropped along with its index rows and `core_sync_state.synced_height` is lowered to just below the wallet's birth height, so the next SPV start rescans it; an undecodable unconfirmed record fails the migration instead. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Index raw inputs so late output ownership can repair the spending history,
//! and keep each record's pre-repair blob so every repair stays reversible.

pub fn migration() -> String {
"CREATE TABLE core_transaction_inputs (
wallet_id BLOB NOT NULL,
txid BLOB NOT NULL,
outpoint BLOB NOT NULL,
PRIMARY KEY (wallet_id, txid, outpoint),
FOREIGN KEY (wallet_id, txid) REFERENCES core_transactions(wallet_id, txid) ON DELETE CASCADE
);
CREATE INDEX idx_core_transaction_inputs_outpoint
ON core_transaction_inputs(wallet_id, outpoint);
CREATE TABLE core_transaction_record_originals (
wallet_id BLOB NOT NULL,
txid BLOB NOT NULL,
record_blob BLOB NOT NULL,
PRIMARY KEY (wallet_id, txid),
FOREIGN KEY (wallet_id) REFERENCES wallets(wallet_id) ON DELETE CASCADE
);"
.to_owned()
}
Loading
Loading