Summary
ActivityListModel handles wallet transaction notifications on the notifying thread and directly mutates a model consumed by QML and QSortFilterProxyModel on the GUI thread. This violates the model/view threading contract and explains two rare Activity-page crashes observed on Qt 6.4.2.
This is present on the current qt6 tip, 3ce18e1e.
Crash evidence
One ASan crash observed ActivityListModel::data() reading a null transaction and dereferencing it in updateTransactionStatus():
#0 ActivityListModel::updateTransactionStatus(QSharedPointer<Transaction>) const
qml/models/activitylistmodel.cpp:38
#1 ActivityListModel::data(QModelIndex const&, int) const
qml/models/activitylistmodel.cpp:66
#2 QSortFilterProxyModel::data(QModelIndex const&, int) const
A second report initially had only Qt offsets while posted events were being drained. I resolved them against the exact Debian Qt 6.4.2 debuginfo for build ID 6976b895aa51ea4619aaa4d7f19f2b055ad05f7d:
libQt6Core.so.6+0x2b9b97 QModelIndex::parent()
libQt6Core.so.6+0x2f25ad QSortFilterProxyModelPrivate::_q_sourceDataChanged(...)
libQt6Core.so.6+0x30a352 postEventSourceDispatch
That places the second crash in the Activity proxy processing a source-model update, corroborating the same race rather than an unrelated QObject-lifetime bug.
Cause
ActivityListModel::subscribeToCoreSignals() calls updateTransaction() directly from the wallet transaction-changed callback. WalletQmlModel::handleTransactionChanged() forwards the wallet callback without a queued GUI-thread hop.
Wallet notifications may arrive from validation/network threads, so ActivityListModel can modify m_transactions, call beginInsertRows() / endInsertRows(), and emit dataChanged() while the GUI thread and proxy are reading the same model.
The established insertion paths construct non-null QSharedPointer<Transaction> values. That makes the observed null element more plausibly a symptom of concurrent container/model access than a normal value inserted by application logic.
Other wallet-signal handlers already use QMetaObject::invokeMethod(..., Qt::QueuedConnection), providing an in-tree pattern for the fix.
Impact
Rare hard crashes while scrolling, opening, or updating the Activity list. A null check in data() would avoid one dereference but would not make the model or proxy thread-safe.
Suggested fix
Marshal every ActivityListModel mutation caused by wallet notifications onto the model/GUI thread before touching m_transactions or emitting model signals. Add thread-affinity assertions around mutation paths and retain defensive null checks where appropriate.
A stress regression should deliver transaction-change notifications from a worker thread while the proxy/delegates repeatedly read and filter the model.
Summary
ActivityListModelhandles wallet transaction notifications on the notifying thread and directly mutates a model consumed by QML andQSortFilterProxyModelon the GUI thread. This violates the model/view threading contract and explains two rare Activity-page crashes observed on Qt 6.4.2.This is present on the current
qt6tip, 3ce18e1e.Crash evidence
One ASan crash observed
ActivityListModel::data()reading a null transaction and dereferencing it inupdateTransactionStatus():A second report initially had only Qt offsets while posted events were being drained. I resolved them against the exact Debian Qt 6.4.2 debuginfo for build ID
6976b895aa51ea4619aaa4d7f19f2b055ad05f7d:That places the second crash in the Activity proxy processing a source-model update, corroborating the same race rather than an unrelated QObject-lifetime bug.
Cause
ActivityListModel::subscribeToCoreSignals()callsupdateTransaction()directly from the wallet transaction-changed callback.WalletQmlModel::handleTransactionChanged()forwards the wallet callback without a queued GUI-thread hop.Wallet notifications may arrive from validation/network threads, so
ActivityListModelcan modifym_transactions, callbeginInsertRows()/endInsertRows(), and emitdataChanged()while the GUI thread and proxy are reading the same model.The established insertion paths construct non-null
QSharedPointer<Transaction>values. That makes the observed null element more plausibly a symptom of concurrent container/model access than a normal value inserted by application logic.Other wallet-signal handlers already use
QMetaObject::invokeMethod(..., Qt::QueuedConnection), providing an in-tree pattern for the fix.Impact
Rare hard crashes while scrolling, opening, or updating the Activity list. A null check in
data()would avoid one dereference but would not make the model or proxy thread-safe.Suggested fix
Marshal every
ActivityListModelmutation caused by wallet notifications onto the model/GUI thread before touchingm_transactionsor emitting model signals. Add thread-affinity assertions around mutation paths and retain defensive null checks where appropriate.A stress regression should deliver transaction-change notifications from a worker thread while the proxy/delegates repeatedly read and filter the model.