Skip to content

ActivityListModel mutates from wallet notification threads and can crash its proxy #870

Description

@johnny9

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    Projects

    • Status
      Review

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions