Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
e5e34f5
feat(unified-sdk-jni): bind the paged wallet inventory and outpoint c…
HashEngineering Sep 18, 2026
f59bb8c
fix(kotlin-sdk): reconcile the TXO store against the engine
HashEngineering Sep 18, 2026
8569384
fix(kotlin-sdk): send the full account tuple in TXO classification qu…
HashEngineering Oct 1, 2026
52af68f
fix(kotlin-sdk): drop the netAmount-suspects log from the TXO reconcile
HashEngineering Oct 2, 2026
642fa99
feat(unified-sdk-jni): deliver the engine's credit verdicts to Kotlin
HashEngineering Oct 2, 2026
1414087
fix(kotlin-sdk): write TXOs the engine did not credit as spent at cre…
HashEngineering Oct 2, 2026
b26d93f
style(unified-sdk-jni): rustfmt wallet_manager.rs
HashEngineering Oct 2, 2026
fb2f641
feat(kotlin-sdk): count committed persistence rounds and page unspent…
HashEngineering Oct 3, 2026
f097230
fix(kotlin-sdk): reconcile the TXO store the way Swift does
HashEngineering Oct 3, 2026
b646aff
fix(kotlin-sdk): gate and schedule the TXO reconcile the way Swift does
HashEngineering Oct 3, 2026
420996c
fix(kotlin-sdk): gate the TXO reconcile on the engine's wallet watermark
HashEngineering Oct 3, 2026
6e795dd
Merge branch 'v5.1-dev' into feat/kotlin-sdk-txo-reconcile-4638-port
HashEngineering Oct 10, 2026
e6ad3a4
fix(kotlin-sdk): resolve reconcile TXO owners the way the restore does
HashEngineering Oct 10, 2026
4035de5
fix(kotlin-sdk): key reconcile log references with a per-process secret
HashEngineering Oct 10, 2026
94c915a
test(unified-sdk-jni): pin the reconcile transport to a shared golden…
HashEngineering Oct 10, 2026
0c674bc
fix(kotlin-sdk): require every routing field of an engine inventory row
HashEngineering Oct 10, 2026
fe26713
fix(kotlin-sdk): refuse reconcile flips while a persistence round is …
HashEngineering Oct 16, 2026
97ec47a
fix(kotlin-sdk): reset the reconcile steady-state edge when SPV stops
HashEngineering Oct 16, 2026
92fe869
Merge remote-tracking branch 'origin/v5.1-dev' into feat/kotlin-sdk-t…
HashEngineering Oct 17, 2026
da997b5
Merge remote-tracking branch 'origin/v5.1-dev' into feat/kotlin-sdk-t…
HashEngineering Oct 18, 2026
1f8df04
feat(platform-wallet): own the TXO-store reconcile policy in the wallet
HashEngineering Oct 19, 2026
d4a577e
feat(platform-wallet): expose the TXO-store reconcile policy over the…
HashEngineering Oct 19, 2026
0a62646
refactor(kotlin-sdk): ask the Rust wallet for every TXO reconcile dec…
HashEngineering Oct 19, 2026
b998f90
fix(kotlin-sdk): never lose the native reconcile claim to a cancelled…
HashEngineering Oct 20, 2026
8a9a3f7
style(platform-wallet-ffi): import the UTXO page types the shared hel…
HashEngineering Oct 20, 2026
9e7711d
Merge remote-tracking branch 'origin/v5.1-dev' into feat/kotlin-sdk-t…
HashEngineering Oct 20, 2026
3a73b8b
fix(platform-wallet): let a wallet the host skips stay due
HashEngineering Oct 25, 2026
ee82972
fix(platform-wallet): keep a removed wallet's reconcile cadence forgo…
HashEngineering Oct 25, 2026
1c37445
fix(platform-wallet): release a reconcile claim when begin is dropped
HashEngineering Oct 25, 2026
133ffdf
fix(platform-wallet): hold the engine still until a spend verdict is …
HashEngineering Oct 25, 2026
021461d
Merge remote-tracking branch 'origin/v5.1-dev' into feat/kotlin-sdk-t…
HashEngineering Oct 25, 2026
7764daf
fix(platform-wallet-ffi): release held reconcile verdicts without the…
HashEngineering Oct 25, 2026
d7a2674
fix(kotlin-sdk): never skip the reconcile finish or leak a held-verdi…
HashEngineering Oct 25, 2026
9e6b720
fix(rs-unified-sdk-jni): never strand a reconcile claim on a failed b…
HashEngineering Oct 28, 2026
20fa455
fix(platform-wallet-ffi): keep reconcile panics out of the C ABI
HashEngineering Oct 28, 2026
165bdde
fix(platform-wallet): read the SPV run session after the steady-state…
HashEngineering Oct 28, 2026
d88079e
fix(kotlin-sdk): check the reconcile epoch again before claiming the …
HashEngineering Oct 28, 2026
5840b1c
fix(kotlin-sdk): fail the reconcile inventory decode on any missing f…
HashEngineering Oct 28, 2026
d30f0d4
fix(kotlin-sdk): keep amount totals out of the reconcile summary log
HashEngineering Oct 28, 2026
f73b082
Merge remote-tracking branch 'origin/v5.1-dev' into feat/kotlin-sdk-t…
HashEngineering Oct 28, 2026
aec38de
refactor(swift-sdk): ask the Rust wallet for every TXO reconcile deci…
HashEngineering Oct 28, 2026
505eafd
fix(rs-unified-sdk-jni): zero outHeld before any classify check can r…
HashEngineering Oct 28, 2026
252f386
fix(swift-sdk): write no spend verdict once a reconcile run is stopped
HashEngineering Oct 28, 2026
7c6675c
fix(platform-wallet-ffi): catch panics in the utxo page and classify …
HashEngineering Oct 29, 2026
1f8013a
fix(rs-unified-sdk-jni): fail a verdict round whose buffer is null
HashEngineering Oct 29, 2026
1d8cece
feat(swift-sdk): keyed log references for public identifiers
HashEngineering Oct 29, 2026
f322265
fix(platform-wallet): hold the engine guard across each heal page's w…
HashEngineering Oct 29, 2026
34de862
fix(platform-wallet): measure heal pages against the run's own tip
HashEngineering Oct 29, 2026
9def6fe
perf(platform-wallet): walk history only for spend-verdict candidates
HashEngineering Oct 29, 2026
cec231b
fix(kotlin-sdk): launch one reconcile run per wallet at a time
HashEngineering Oct 29, 2026
2f7c9b0
perf(platform-wallet): build one mined-spend index per reconcile run
HashEngineering Oct 30, 2026
b67af21
fix(platform-wallet): mark a coin spent only for a ChainLocked spender
HashEngineering Oct 30, 2026
fb745f7
perf(kotlin-sdk): index txos for the unspent reconcile page
HashEngineering Oct 30, 2026
50e49f8
fix(kotlin-sdk): cap the pages one reconcile run reads, resume next run
HashEngineering Oct 30, 2026
ce15088
fix(platform-wallet): keep what ChainLock-dropped spenders spent
HashEngineering Nov 1, 2026
0f902d1
refactor(platform-wallet): own the reconcile page budget and resume p…
HashEngineering Nov 1, 2026
5bace38
fix(platform-wallet): re-deliver the coins a chain truncation gives back
HashEngineering Nov 1, 2026
e311479
refactor(platform-wallet): keep only owned inputs of finalized spende…
HashEngineering Nov 1, 2026
d60d840
fix(platform-wallet): re-deliver restored coins without their funding…
HashEngineering Nov 2, 2026
5026968
fix(swift-sdk): keep SDKLogValue's cases for downstream exhaustive sw…
HashEngineering Nov 2, 2026
a8752bb
fix(platform-wallet): let a restored coin beat an older spend in the …
HashEngineering Nov 2, 2026
809031e
fix(platform-wallet-ffi): let a restored coin override the spends hos…
HashEngineering Nov 2, 2026
6a0431c
revert(kotlin-sdk): move the txos reconcile paging index to its own PR
HashEngineering Nov 2, 2026
f70815e
test(kotlin-sdk): expect the restored count in the credit-verdict log…
HashEngineering Nov 2, 2026
f80233a
fix(platform-wallet): apply restored coins after every link and sweep…
HashEngineering Nov 3, 2026
084e421
fix(platform-wallet): end a restoration at a newer record's spend and…
HashEngineering Nov 3, 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
3 changes: 3 additions & 0 deletions Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,32 @@ abstract class NativePersistenceBridge {
*/
open fun onWalletChangesetChainLockHeight(walletId: ByteArray, height: Int): Int = 0

/**
* The engine's credit verdicts for the round (dashpay/platform#4638):
* every `Received` / `Change` output of the round's records that the
* engine did NOT credit to the owning account, and every coin a chain
* truncation gave back. Fired inside the begin/end bracket BEFORE the
* changeset callback, and only on rounds that carry at least one
* verdict or restored coin, so an implementation has them in
* hand when the round's [onWalletChangesetUtxoAdded] entries arrive.
* Descriptor `([B[B[B[II)I`.
*
* [outpoints] is [count] 36-byte keys (32-byte wire-order txid plus a
* little-endian vout) packed into one array; [verdicts] holds one
* `UTXO_CREDIT_VERDICT_*` code per outpoint (1 observed spent, 2
* doomed, 3 uncredited, 4 restored) and [spentAtHeights] the observed spending
* block's height for code 1, 0 otherwise. Purely additive: a host that
* ignores the verdicts writes exactly what it wrote before the slot
* existed.
*/
open fun onWalletChangesetUtxoVerdicts(
walletId: ByteArray,
outpoints: ByteArray,
verdicts: ByteArray,
spentAtHeights: IntArray,
count: Int,
): Int = 0

/**
* The wallet's DashPay coreHeight backfill record — the durable half of
* the contact rescan guard (dashpay/platform#4302). Fired inside the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,234 @@ internal object WalletManagerNative {
gapLimit: Int,
)

// ── TXO-store reconcile (dashpay/platform#4638) ─────────────────────
//
// One export = one `platform-wallet` call. The schedule, the start
// gates, the heal verdicts, the stale-page retry limit, the page budget
// and where a run resumes all live in
// `platform_wallet::manager::txo_reconcile`; Kotlin pages its own Room
// rows, resolves owners, writes, and reports each page it applied.

/** [walletManagerTxoReconcileBegin]: the run may start
* (`TXO_RECONCILE_GATE_RUN`). */
const val TXO_RECONCILE_GATE_RUN = 0

/** [walletManagerTxoReconcileBegin]'s `outStart[0]`, mirroring
* rs-platform-wallet-ffi `TXO_RECONCILE_START_*`: a new cycle at the
* heal pass. */
const val TXO_RECONCILE_START_HEAL = 0

/** The heal pass, where the wallet's last run stopped. The native
* reconciler keeps that place; the host passes no cursor. */
const val TXO_RECONCILE_START_HEAL_RESUMED = 1

/** Straight to the classify pass, after the store cursor
* [walletManagerTxoReconcileBegin] hands back in `outStoreAfter[0]`. */
const val TXO_RECONCILE_START_CLASSIFY = 2

/** The longest store cursor
* [walletManagerTxoReconcileClassifyPageApplied] keeps for the
* wallet's next run (rs-platform-wallet-ffi
* `TXO_RECONCILE_STORE_CURSOR_MAX`). */
const val TXO_RECONCILE_STORE_CURSOR_MAX = 64

/**
* The wallets whose reconcile is due now, as concatenated 32-byte ids
* (`platform_wallet_manager_txo_reconcile_due_wallets`). Called on every
* SPV progress poll, running or not: the poll is the schedule's clock,
* and the native side detects the steady-state edge and applies the
* cadence. Each returned wallet counts as scheduled now.
*/
external fun walletManagerTxoReconcileDueWallets(managerHandle: Long): ByteArray?

/**
* Claim [walletId] for one reconcile run and check that it may start
* (`platform_wallet_manager_txo_reconcile_begin`). Returns
* [TXO_RECONCILE_GATE_RUN] or a `TXO_RECONCILE_SKIP_*` reason (see
* [org.dashfoundation.dashsdk.wallet.TxoReconcileSkipReason.fromGateCode]);
* on [TXO_RECONCILE_GATE_RUN] `outTipHeight[0]` is the scan tip the run
* is measured against (the native claim keeps it), and the claim holds
* until [walletManagerTxoReconcileFinish].
*
* On [TXO_RECONCILE_GATE_RUN] `outStart[0]` is where the run starts, as
* the native reconciler decides it from the wallet's checkpoint:
* [TXO_RECONCILE_START_HEAL], [TXO_RECONCILE_START_HEAL_RESUMED] or
* [TXO_RECONCILE_START_CLASSIFY]. For the last, `outStoreAfter[0]` is
* the store cursor to start after, the bytes the host last passed to
* [walletManagerTxoReconcileClassifyPageApplied] (empty: the first
* row); otherwise it is null. [outTipHeight] and [outStart] are
* `IntArray(1)`, [outStoreAfter] an `arrayOfNulls<ByteArray>(1)`,
* checked before the claim is taken.
*/
external fun walletManagerTxoReconcileBegin(
managerHandle: Long,
walletId: ByteArray,
outTipHeight: IntArray,
outStart: IntArray,
outStoreAfter: Array<ByteArray?>,
): Int

/**
* Release the claim a [TXO_RECONCILE_GATE_RUN] from
* [walletManagerTxoReconcileBegin] granted; [ran] is whether the run went
* ahead, which restarts the wallet's cadence
* (`platform_wallet_manager_txo_reconcile_finish`).
*/
external fun walletManagerTxoReconcileFinish(managerHandle: Long, walletId: ByteArray, ran: Boolean)

/**
* Whether a reconcile run classifies a page again after it came back
* stale for the [staleInARow]-th time in a row, or stops incomplete
* (`platform_wallet_txo_reconcile_retry_stale_page`, which holds the
* limit).
*/
external fun txoReconcileRetryStalePage(staleInARow: Int): Boolean

/**
* One bounded page of the engine's UTXO inventory for one wallet,
* across every funds account, each row with the engine's heal verdict
* (measured against the run's own tip, which the native claim keeps), as
* JSON `{"utxos":[...],"hasMore":<bool>,"budgetSpent":<bool>}`. A
* serialization shim over one Rust call
* (`platform_wallet_txo_reconcile_heal_page`): account ordering, the
* run's place in the inventory, the page bound, the page budget, the
* address rendering and the verdicts all live in `platform-wallet`.
*
* Paged, not swept whole: a wallet's UTXO count is chain-controlled
* (anyone who knows a watched address can keep sending dust to it), so
* a full-inventory read would let a remote party decide how much this
* process allocates on every steady-state transition and every cadence
* pass. The native side keeps the run's place, so the host passes no
* cursor: each page starts after the last one reported with
* [walletManagerTxoReconcileHealPageApplied] (or where the wallet's last
* run stopped), and a page read again before it is reported is the same
* page. Keep going while `hasMore` is true. Each call is one page of the
* run's budget; `budgetSpent` true means nothing was read and nothing
* is held: the run has used its budget and stops. [limit] caps the rows
* in one page; non-positive asks for the engine's own default, and the
* engine clamps its own maximum, so no bound is mirrored on this side.
*
* Contact watch-only chains (`DashpayExternalAccount`) are absent by
* construction — those coins are the contact's money, not this
* wallet's — so the inventory never offers one to heal.
*
* Each `utxos` row is a
* [org.dashfoundation.dashsdk.persistence.PlatformWalletPersistenceHandler.EngineUtxoRow]:
* the owning account tuple, the txid hex in the same byte order the
* changeset path hands the handler (so hex→bytes reproduces the
* `txos.txid` blob), vout, amount (duffs), the engine's own
* Base58Check address (empty when the script has no address form),
* scriptHex, height, the engine's own `isConfirmed` /
* `isInstantLocked` / `isCoinbase` / `isLocked` flags, and its
* `healVerdict` (`TXO_HEAL_VERDICT_*`).
*
* Only for the wallet's claimed reconcile run. The page comes back with
* the engine's wallet lock still held for reading, so no fork can drop a
* coin it offers before the page is written: the guard is written to
* [outHeld] (a `LongArray(1)`; 0 when nothing is held) only together
* with the page, and is released with [txoReconcileHeldVerdictsFree]
* under the rules [walletManagerTxoReconcileClassifyOutpoints] gives.
*/
external fun walletManagerTxoHealPageJson(
managerHandle: Long,
walletId: ByteArray,
limit: Int,
outHeld: LongArray,
): String?

/**
* Report that the heal page [walletManagerTxoHealPageJson] last returned
* was applied: its store step committed (a page with nothing to write
* counts as applied) and its guard was released. The run's next heal
* page starts after it, and so does the wallet's next run if this one
* stops here (`platform_wallet_txo_reconcile_heal_page_applied`).
* Returns whether the run has budget left for another page.
*
* Resolves [managerHandle] in the native registry, so it is never called
* while a page's guard is held: release first, then report.
*/
external fun walletManagerTxoReconcileHealPageApplied(managerHandle: Long, walletId: ByteArray): Boolean

/**
* Classify a batch of store rows against the engine's live state: the
* reverse half of the reconcile transport, and the reason the paged
* inventory above carries no spent-outpoint list. The caller pages its
* OWN mirror rows and asks about them a batch at a time, so neither
* side ever builds a set over the whole engine inventory.
*
* [queriesJson] is a JSON array of
* [org.dashfoundation.dashsdk.persistence.PlatformWalletPersistenceHandler.OutpointQuery]:
* for each row, the account tuple the STORE files the coin under, its
* txid hex, vout, and the scriptHex the store recorded. The account
* and script are the store's claim — the engine checks both against
* its own pools rather than trusting them, which is why a bare
* outpoint is not enough to ask the question.
*
* Returns one verdict byte per query, positionally — see
* `OUTPOINT_CLASS_*` on
* [org.dashfoundation.dashsdk.persistence.PlatformWalletPersistenceHandler]:
* 0 unknown, 1 unspent, 2 known-uncredited, 3 not-owned. A query whose
* account tag this build cannot map keeps `unknown`; the rest are
* still answered.
*
* Only for the wallet's claimed reconcile run (between
* [walletManagerTxoReconcileBegin] and [walletManagerTxoReconcileFinish]).
* The answers come back with the engine's wallet lock still held for
* reading, so no fork can drop a spender they name before the page is
* applied. The guard is written to [outHeld] (a `LongArray(1)`; 0 when
* nothing is held) only together with the answers. Release it with
* [txoReconcileHeldVerdictsFree] once the page is applied and before
* the next call that takes [managerHandle]: those resolve the handle in
* the native registry, which must never be waited for while the guard
* is held. Finish releases a guard left behind, as a backstop. Call it
* outside the persistence handler's callback exclusion and never from
* inside a persistence callback: the engine calls those callbacks while
* it holds its wallet lock for writing.
*
* Called for every store page the classify pass reads, one with no
* queries (`"[]"`) too: each call is one page of the run's budget, holds
* a guard like any other, and names the page
* [walletManagerTxoReconcileClassifyPageApplied] reports. Once the run
* has used its budget `outBudgetSpent[0]` is true, every answer is
* unknown and nothing is held: the run stops. [outBudgetSpent] is a
* `BooleanArray(1)`.
*/
external fun walletManagerTxoReconcileClassifyOutpoints(
managerHandle: Long,
walletId: ByteArray,
queriesJson: String,
outBudgetSpent: BooleanArray,
outHeld: LongArray,
): ByteArray?

/**
* Report that the store page [walletManagerTxoReconcileClassifyOutpoints]
* last classified was applied and its guard released
* (`platform_wallet_txo_reconcile_classify_page_applied`). [nextAfter] is
* the host's store cursor past that page, at most
* [TXO_RECONCILE_STORE_CURSOR_MAX] bytes, which
* [walletManagerTxoReconcileBegin] hands back to the wallet's next run;
* null when that was the store's last page, which ends the cycle (the
* next run starts at the heal pass again). Returns whether the run has
* budget left for another page.
*
* Resolves [managerHandle] in the native registry, so it is never called
* while a page's guard is held: release first, then report.
*/
external fun walletManagerTxoReconcileClassifyPageApplied(
managerHandle: Long,
walletId: ByteArray,
nextAfter: ByteArray?,
): Boolean

/**
* Release the engine guard [walletManagerTxoHealPageJson] or
* [walletManagerTxoReconcileClassifyOutpoints] handed out. Takes no manager handle and touches no native registry,
* so it completes whatever is queued there. 0 is a no-op; release each
* value once.
*/
external fun txoReconcileHeldVerdictsFree(held: Long)

// ── Core transaction builder (1:1 over `core_wallet_tx_builder_*`) ─
//
// Each step is a thin extern (one export = one FFI call, per
Expand Down
Loading
Loading