diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62c73a98cf2..ee85f2183c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,3 @@
-## Unreleased
-
-### Fixed
-
-- **platform-wallet:** Allow reconciliation of already-loaded asset locks with atomic tracked-lock persistence without requiring unrelated wallet restore support; retain nonterminal recovery state and typed consumption errors.
-
-- **platform-wallet-storage:** Restore confirmed Core spend and finality state on SQLite load so old funding transactions cannot make already-spent outputs selectable again.
-
## [4.2.0-beta.7](https://github.com/dashpay/platform/compare/v4.2.0-beta.6...v4.2.0-beta.7) (2026-09-29)
diff --git a/Cargo.lock b/Cargo.lock
index a593de4f7f5..bdb7d2c4477 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5350,7 +5350,6 @@ dependencies = [
"chacha20poly1305",
"chrono",
"clap",
- "dash-async",
"dash-sdk",
"dashcore",
"dbus-secret-service-keyring-store",
diff --git a/packages/rs-platform-wallet-storage/Cargo.toml b/packages/rs-platform-wallet-storage/Cargo.toml
index fe1ed8c06f6..17dcc10136e 100644
--- a/packages/rs-platform-wallet-storage/Cargo.toml
+++ b/packages/rs-platform-wallet-storage/Cargo.toml
@@ -41,7 +41,6 @@ platform-wallet = { path = "../rs-platform-wallet", features = [
"eddsa",
], optional = true }
serde = { version = "1", features = ["derive"], optional = true }
-dash-async = { path = "../rs-dash-async", optional = true }
key-wallet = { workspace = true, optional = true }
dashcore = { workspace = true, optional = true }
dpp = { path = "../rs-dpp", optional = true }
@@ -225,7 +224,6 @@ sqlite = [
"dep:platform-wallet",
"dep:serde",
"dep:key-wallet",
- "dep:dash-async",
"dep:dashcore",
"dep:dpp",
"dep:dash-sdk",
diff --git a/packages/rs-platform-wallet-storage/migrations/V019__core_transaction_accounting.rs b/packages/rs-platform-wallet-storage/migrations/V019__core_transaction_accounting.rs
index b92003e8545..d563fd867f0 100644
--- a/packages/rs-platform-wallet-storage/migrations/V019__core_transaction_accounting.rs
+++ b/packages/rs-platform-wallet-storage/migrations/V019__core_transaction_accounting.rs
@@ -1,4 +1,5 @@
-//! Index raw inputs so late output ownership can repair the spending history.
+//! 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 (
@@ -9,6 +10,13 @@ pub fn migration() -> String {
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);"
+ 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()
}
diff --git a/packages/rs-platform-wallet-storage/src/sqlite/error.rs b/packages/rs-platform-wallet-storage/src/sqlite/error.rs
index 2a7a30414b9..9f843edf7bb 100644
--- a/packages/rs-platform-wallet-storage/src/sqlite/error.rs
+++ b/packages/rs-platform-wallet-storage/src/sqlite/error.rs
@@ -34,10 +34,6 @@ pub enum AutoBackupOperation {
/// Errors produced by the wallet-storage SQLite backend.
#[derive(Debug, thiserror::Error)]
pub enum WalletStorageError {
- /// Confirmed Core history could not be replayed into the restored wallet.
- #[error("could not restore confirmed Core history: {0}")]
- CoreHistoryReplay(#[source] dash_async::AsyncError),
-
/// File-system I/O error reaching the database or backup files.
#[error("io error")]
Io(#[from] std::io::Error),
@@ -710,8 +706,7 @@ impl WalletStorageError {
// `ToSqlConversionFailure`, `InvalidColumnIndex`) — is a
// logic bug, not a contention failure.
Self::Sqlite(_) => false,
- Self::CoreHistoryReplay(_)
- | Self::Io(_)
+ Self::Io(_)
| Self::Migration(_)
| Self::IntegrityCheckFailed { .. }
| Self::IntegrityCheckRunFailed { .. }
@@ -876,7 +871,6 @@ impl WalletStorageError {
| Self::UnownedIdentityHasRegistrationIndex { .. }
| Self::EmptyUtxoScript { .. }
| Self::EmptyPoolAddressScript { .. }
- | Self::CoreHistoryReplay(_)
| Self::DatabasePathIsSymlink { .. } => PersistenceErrorKind::Fatal,
}
}
@@ -897,7 +891,6 @@ impl WalletStorageError {
},
Self::Sqlite(_) => "sqlite_other",
Self::FlushRetryable { .. } => "flush_retryable",
- Self::CoreHistoryReplay(_) => "core_history_replay",
Self::Io(_) => "io",
Self::Migration(_) => "migration",
Self::IntegrityCheckFailed { .. } => "integrity_check_failed",
diff --git a/packages/rs-platform-wallet-storage/src/sqlite/migrations.rs b/packages/rs-platform-wallet-storage/src/sqlite/migrations.rs
index e152a68a97a..ddd18caad25 100644
--- a/packages/rs-platform-wallet-storage/src/sqlite/migrations.rs
+++ b/packages/rs-platform-wallet-storage/src/sqlite/migrations.rs
@@ -10,6 +10,7 @@ use crate::sqlite::error::WalletStorageError;
use refinery_core::error::WrapMigrationError;
mod legacy_v008;
+mod legacy_v019;
// Generates a `migrations` module with `runner()`; path is relative to
// the crate root.
@@ -78,7 +79,7 @@ impl refinery_core::traits::sync::Transaction for MigrationTransaction<'_> {
} else if query == self.pool_sql {
legacy_v008::convert_pools(&self.tx)?;
} else if query == self.history_sql {
- super::schema::core_history::migrate(&self.tx)?;
+ legacy_v019::repair_history(&self.tx)?;
}
count += 1;
}
diff --git a/packages/rs-platform-wallet-storage/src/sqlite/migrations/legacy_v019.rs b/packages/rs-platform-wallet-storage/src/sqlite/migrations/legacy_v019.rs
new file mode 100644
index 00000000000..e1bf7fcd5dc
--- /dev/null
+++ b/packages/rs-platform-wallet-storage/src/sqlite/migrations/legacy_v019.rs
@@ -0,0 +1,569 @@
+//! Frozen V019 history repair. It backfills the raw-input index and corrects
+//! stored accounting for databases migrating from V018 or earlier.
+//!
+//! Frozen on purpose: the live per-round repair in `schema::core_history` may
+//! evolve, but V019 must keep doing exactly what it did when it shipped. Only
+//! the low-level blob codec is shared; `TransactionRecord`'s encoding is owned
+//! upstream (key-wallet) and cannot be frozen here. Do not edit.
+
+use std::collections::BTreeMap;
+
+use dashcore::hashes::Hash;
+use dashcore::{Address, OutPoint, ScriptBuf, Txid};
+use key_wallet::managed_account::transaction_record::{
+ InputDetail, OutputDetail, OutputRole, TransactionDirection, TransactionRecord,
+};
+use key_wallet::transaction_checking::{TransactionContext, TransactionType};
+use platform_wallet::wallet::platform_wallet::WalletId;
+use rusqlite::{params, OptionalExtension, Transaction};
+
+use crate::sqlite::error::WalletStorageError;
+use crate::sqlite::schema::{blob, id32, wallets};
+use crate::sqlite::util::safe_cast::i64_to_u64;
+
+/// Read a stored record; undecodable bytes are an error the caller classifies.
+fn read_record(
+ tx: &Transaction<'_>,
+ wallet_id: &WalletId,
+ txid: &Txid,
+) -> Result