fix(kotlin-sdk): act on swept transactions in the Room store - #4590
fix(kotlin-sdk): act on swept transactions in the Room store#4590romchornyi wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🕓 Ready for review — 50 ahead in queue (commit 74bdb64) |
8918079 to
1fb1a17
Compare
c2d5055 to
55cbf22
Compare
The Room mirror of the storage contract, plus the JNI trampoline that delivers a round's sweeps. Kotlin deletes rather than marks — Room rows here are wallet-scoped, so there is no shared row to keep inert the way SwiftData needs — but the order is load-bearing: hold before delete, because the foreign key nulls the very column that finds a released coin's rows. Held inputs become pending-input tombstones carrying the winner and, when it was mined, its height; a co-swept child's claim on its parent's output goes with the batch; the drain guards `isSpent` against a tombstone; and every restore path excludes what a sweep removed. `NativePersistenceBridge` gains the `CORE_SWEEP_REMOVAL` constant on the class whose default implementation refuses the round: a subclass that declares the bit without overriding the callback fails its round rather than silently dropping the removal. Kotlin deliberately does not declare `DASHPAY_PAYMENTS` — this store has no payments overlay, and saying so is what keeps Rust's flip from being staged onto a round that would drop it. The JNI half must ship with it. `rs-unified-sdk-jni` adds the sweeps trampoline with a `with_local_frame` per batch, its descriptor in the bridge method table's smoke check, and reorders `transactions` ahead of `utxos_added` because the swept-row guard reads a state the transaction pass writes. Kotlin alone is safe (the declared ∩ structural intersection withholds the bit until the slot is wired) but JNI alone is a hard init failure by that same smoke check, and the SDK ships both from one revision. Room goes to schema 13 with migrations 10→11→12→13 and their generated JSON kept as reviewed. Tests: ~2,000 lines of Robolectric coverage — the capability default refusing a hand-declared round, a release naming more outpoints than SQLite can bind in one statement, co-swept twins, detached tombstones with a swept winner, cross-round reinstatement, the `releaseByOutpoint` spender guard, and asset-lock Consumed(4) terminal guards — plus migration tests validating against the schema JSONs. 398 unit tests pass (`./gradlew :sdk:testDebugUnitTest`). The test file is the union of this branch's cases and those `#4356` and the marketplace work added to the same regions while this PR was open; all 143 test and helper functions from both sides are present.
1fb1a17 to
73a9ea7
Compare
55cbf22 to
74bdb64
Compare
Issue being fixed or feature implemented
The Android half of the same contract. Until it lands, the Kotlin host never attests
CORE_SWEEP_REMOVAL, so Rust fail-closes and an Android wallet freezes its sync watermark on the first sweep it meets — funds-safe, but a user-visible stall.What was done?
The store
Kotlin deletes rather than marks: Room rows here are wallet-scoped, so there is no shared row to keep inert the way SwiftData needs. The order is load-bearing — hold before delete, because the foreign key nulls the very column that finds a released coin's rows.
Held inputs become pending-input tombstones carrying the winner and, when it was mined, its height; a co-swept child's claim on its parent's output goes with the batch; the drain guards
isSpentagainst a tombstone; and every restore path excludes what a sweep removed.Capability
NativePersistenceBridgegains theCORE_SWEEP_REMOVALconstant on the class whose default implementation refuses the round: a subclass that declares the bit without overriding the callback fails its round rather than silently dropping the removal.Kotlin deliberately does not declare
DASHPAY_PAYMENTS— this store has no payments overlay, and saying so is what keeps Rust's flip from being staged onto a round that would drop it.JNI — why it ships in the same PR
rs-unified-sdk-jniadds the sweeps trampoline with awith_local_frameper batch, its descriptor in the bridge method table's smoke check, and reorderstransactionsahead ofutxos_addedbecause the swept-row guard reads a state the transaction pass writes.Kotlin alone is safe (the declared ∩ structural intersection withholds the bit until the slot is wired), but JNI alone is a hard init failure by that same smoke check — and the SDK ships both from one revision, so they travel together.
Schema
Room goes to schema 13 with migrations 10→11→12→13 and their generated JSON kept as reviewed.
How Has This Been Tested?
ANDROID_HOME=$HOME/Library/Android/sdk ./gradlew :sdk:testDebugUnitTest— 398 unit tests pass, andcargo check -p rs-unified-sdk-jniis clean.Robolectric coverage (no native needed) for: the capability default refusing a hand-declared round, a release naming more outpoints than SQLite can bind in one statement, co-swept twins, detached tombstones with a swept winner, cross-round reinstatement, the
releaseByOutpointspender guard, and asset-lock Consumed(4) terminal guards — plus migration tests validating against the schema JSONs.The test file is the union of this branch's cases and those
#4356and the marketplace work added to the same regions while this PR was open; all 143 test and helper functions from both sides are present, verified function by function.Breaking Changes
None.
NativePersistenceBridgegains anopen funwith a fail-closed default, so an out-of-tree subclass that declares the bit without implementing it fails its round rather than silently losing the removal — deliberate, and the safe direction.Checklist:
For repository code-owners and collaborators only