diff --git a/src/gridcoin/voting/registry.cpp b/src/gridcoin/voting/registry.cpp index 9c3a7468e9..748a70cb71 100644 --- a/src/gridcoin/voting/registry.cpp +++ b/src/gridcoin/voting/registry.cpp @@ -1046,6 +1046,9 @@ void PollRegistry::AddVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED( *poll_ref->m_ptitle, poll_ref->Votes().size()); + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } @@ -1068,6 +1071,10 @@ void PollRegistry::AddVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED( return; } poll_ref->LinkVote(ctx.m_tx.GetHash()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } @@ -1075,6 +1082,8 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR { const auto payload = ctx->SharePayloadAs(); + int64_t poll_time = payload->m_poll.m_timestamp; + m_polls.erase(ToLower(payload->m_poll.m_title)); m_polls_by_txid.erase(ctx.m_tx.GetHash()); @@ -1085,6 +1094,10 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR payload->m_poll.m_title, m_polls.size()); + if (fQtActive) { + uiInterface.NewPollReceived(poll_time);; + } + } void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED(cs_main, PollRegistry::cs_poll_registry) @@ -1100,6 +1113,10 @@ void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR ctx.m_tx.GetHash().GetHex(), *poll_ref->m_ptitle, poll_ref->Votes().size()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } return; @@ -1114,6 +1131,10 @@ void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR if (PollReference* poll_ref = TryBy(title)) { poll_ref->UnlinkVote(ctx.m_tx.GetHash()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } diff --git a/src/node/ui_interface.cpp b/src/node/ui_interface.cpp index 2a7d5ec950..2aaab60d6f 100644 --- a/src/node/ui_interface.cpp +++ b/src/node/ui_interface.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. +#include "uint256.h" #include #include @@ -25,6 +26,7 @@ struct UISignals { boost::signals2::signal MRCChanged; boost::signals2::signal BeaconChanged; boost::signals2::signal NewPollReceived; + boost::signals2::signal NewVoteReceived; boost::signals2::signal NotifyScraperEvent; boost::signals2::signal ThreadSafeAskFee; boost::signals2::signal ThreadSafeHandleURI; @@ -53,6 +55,7 @@ ADD_SIGNALS_IMPL_WRAPPER(AccrualChangedFromStakeOrMRC); ADD_SIGNALS_IMPL_WRAPPER(MRCChanged); ADD_SIGNALS_IMPL_WRAPPER(BeaconChanged); ADD_SIGNALS_IMPL_WRAPPER(NewPollReceived); +ADD_SIGNALS_IMPL_WRAPPER(NewVoteReceived); ADD_SIGNALS_IMPL_WRAPPER(NotifyScraperEvent); ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeAskFee); ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeHandleURI); @@ -78,6 +81,7 @@ void CClientUIInterface::AccrualChangedFromStakeOrMRC() { return g_ui_signals.Ac void CClientUIInterface::MRCChanged() { return g_ui_signals.MRCChanged(); } void CClientUIInterface::BeaconChanged() { return g_ui_signals.BeaconChanged(); } void CClientUIInterface::NewPollReceived(int64_t poll_time) { return g_ui_signals.NewPollReceived(poll_time); } +void CClientUIInterface::NewVoteReceived(const uint256& poll_txid) { return g_ui_signals.NewVoteReceived(poll_txid); } void CClientUIInterface::NotifyAlertChanged(const uint256 &hash, ChangeType status) { return g_ui_signals.NotifyAlertChanged(hash, status); } void CClientUIInterface::NotifyScraperEvent(const scrapereventtypes& ScraperEventtype, ChangeType status, const std::string& message) { return g_ui_signals.NotifyScraperEvent(ScraperEventtype, status, message); } diff --git a/src/node/ui_interface.h b/src/node/ui_interface.h index 8ae74def0c..7cbcffd5e6 100644 --- a/src/node/ui_interface.h +++ b/src/node/ui_interface.h @@ -135,6 +135,9 @@ class CClientUIInterface /** New poll received **/ ADD_SIGNALS_DECL_WRAPPER(NewPollReceived, void, int64_t poll_time); + /** New vote received **/ + ADD_SIGNALS_DECL_WRAPPER(NewVoteReceived, void, const uint256& poll_txid); + /** * New, updated or cancelled alert. * @note called with lock cs_mapAlerts held. diff --git a/src/qt/forms/voting/pollcard.ui b/src/qt/forms/voting/pollcard.ui index 112505846d..2bad4f8d81 100644 --- a/src/qt/forms/voting/pollcard.ui +++ b/src/qt/forms/voting/pollcard.ui @@ -298,6 +298,13 @@ + + + + Stale + + + diff --git a/src/qt/res/stylesheets/dark_stylesheet.qss b/src/qt/res/stylesheets/dark_stylesheet.qss index 4290e39ed7..6dde9b1ddf 100644 --- a/src/qt/res/stylesheets/dark_stylesheet.qss +++ b/src/qt/res/stylesheets/dark_stylesheet.qss @@ -994,6 +994,14 @@ PollCard #invalidLabel { color: rgb(150, 0, 0); } +PollCard #staleLabel { + border: 0.065em solid rgb(240, 0, 0); + border-radius: 0.65em; + padding: 0.1em 0.3em; + color: rgb(255, 255, 255); + background-color: rgb(240, 0, 0); +} + PollCard #remainingLabel, PollResultChoiceItem #percentageLabel, PollResultChoiceItem #weightLabel, diff --git a/src/qt/res/stylesheets/light_stylesheet.qss b/src/qt/res/stylesheets/light_stylesheet.qss index 9d2a6e32b0..a81de84aba 100644 --- a/src/qt/res/stylesheets/light_stylesheet.qss +++ b/src/qt/res/stylesheets/light_stylesheet.qss @@ -969,6 +969,14 @@ PollCard #invalidLabel { color: rgb(150, 0, 0); } +PollCard #staleLabel { + border: 0.065em solid rgb(240, 0, 0); + border-radius: 0.65em; + padding: 0.1em 0.3em; + color: rgb(255, 255, 255); + background-color: rgb(240, 0, 0); +} + PollCard #remainingLabel, PollResultChoiceItem #percentageLabel, PollResultChoiceItem #weightLabel, diff --git a/src/qt/voting/pollcard.cpp b/src/qt/voting/pollcard.cpp index b08df8c9e6..6680209bdd 100644 --- a/src/qt/voting/pollcard.cpp +++ b/src/qt/voting/pollcard.cpp @@ -83,6 +83,12 @@ PollCard::PollCard(const PollItem& poll_item, QWidget* parent) ui->invalidLabel->show(); } + if (poll_item.m_stale) { + ui->staleLabel->show(); + } else { + ui->staleLabel->hide(); + } + ui->topAnswerLabel->setText(poll_item.m_top_answer); if (!poll_item.m_finished) { diff --git a/src/qt/voting/polltablemodel.cpp b/src/qt/voting/polltablemodel.cpp index ab423e3e99..14f3bbe8ed 100644 --- a/src/qt/voting/polltablemodel.cpp +++ b/src/qt/voting/polltablemodel.cpp @@ -13,169 +13,179 @@ using namespace GRC; namespace { -class PollTableDataModel : public QAbstractTableModel +PollTableDataModel::PollTableDataModel() { -public: - PollTableDataModel() - { - qRegisterMetaType>(); - qRegisterMetaType(); - - m_columns - << tr("Title") - << tr("Poll Type") - << tr("Duration") - << tr("Expiration") - << tr("Weight Type") - << tr("Votes") - << tr("Total Weight") - << tr("% of Active Vote Weight") - << tr("Validated") - << tr("Top Answer"); - } + qRegisterMetaType>(); + qRegisterMetaType(); + + m_columns + << tr("Title") + << tr("Poll Type") + << tr("Duration") + << tr("Expiration") + << tr("Weight Type") + << tr("Votes") + << tr("Total Weight") + << tr("% of Active Vote Weight") + << tr("Validated") + << tr("Top Answer") + << tr("Stale Results"); +} - int rowCount(const QModelIndex &parent) const override - { - if (parent.isValid()) { - return 0; - } - return m_rows.size(); +int PollTableDataModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; } + return m_rows.size(); +} - int columnCount(const QModelIndex &parent) const override - { - if (parent.isValid()) { - return 0; - } - return m_columns.size(); +int PollTableDataModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; } + return m_columns.size(); +} - QVariant data(const QModelIndex &index, int role) const override - { - if (!index.isValid()) { - return QVariant(); - } - - const PollItem* row = static_cast(index.internalPointer()); - - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case PollTableModel::Title: - return row->m_title; - case PollTableModel::PollType: - if (row->m_version >= 3) { - return row->m_type_str; - } else { - return QString{}; - } - case PollTableModel::Duration: - return row->m_duration; - case PollTableModel::Expiration: - return GUIUtil::dateTimeStr(row->m_expiration); - case PollTableModel::WeightType: - return row->m_weight_type_str; - case PollTableModel::TotalVotes: - return row->m_total_votes; - case PollTableModel::TotalWeight: - return QString::number(row->m_total_weight); - case PollTableModel::VotePercentAVW: - return QString::number(row->m_vote_percent_AVW, 'f', 4); - case PollTableModel::Validated: - return row->m_validated; - case PollTableModel::TopAnswer: - return row->m_top_answer; - } // no default case, so the compiler can warn about missing cases - assert(false); - - case Qt::TextAlignmentRole: - switch (index.column()) { - case PollTableModel::Duration: - // Pass-through case - case PollTableModel::TotalVotes: - // Pass-through case - case PollTableModel::TotalWeight: - // Pass-through case - case PollTableModel::VotePercentAVW: - // Pass-through case - case PollTableModel::Validated: - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } - break; - - case PollTableModel::SortRole: - switch (index.column()) { - case PollTableModel::Title: - return row->m_title; - case PollTableModel::PollType: - return row->m_type_str; - case PollTableModel::Duration: - return row->m_duration; - case PollTableModel::Expiration: - return row->m_expiration; - case PollTableModel::WeightType: - return row->m_weight_type_str; - case PollTableModel::TotalVotes: - return row->m_total_votes; - case PollTableModel::TotalWeight: - return QVariant::fromValue(row->m_total_weight); - case PollTableModel::VotePercentAVW: - return QVariant::fromValue(row->m_vote_percent_AVW); - case PollTableModel::Validated: - return row->m_validated; - case PollTableModel::TopAnswer: - return row->m_top_answer; - } // no default case, so the compiler can warn about missing cases - assert(false); - } - +QVariant PollTableDataModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { return QVariant(); } - QVariant headerData(int section, Qt::Orientation orientation, int role) const override - { - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole && section < m_columns.size()) { - return m_columns[section]; - } - } + const PollItem* row = static_cast(index.internalPointer()); - return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case PollTableModel::Title: + return row->m_title; + case PollTableModel::PollType: + if (row->m_version >= 3) { + return row->m_type_str; + } else { + return QString{}; + } + case PollTableModel::Duration: + return row->m_duration; + case PollTableModel::Expiration: + return GUIUtil::dateTimeStr(row->m_expiration); + case PollTableModel::WeightType: + return row->m_weight_type_str; + case PollTableModel::TotalVotes: + return row->m_total_votes; + case PollTableModel::TotalWeight: + return QString::number(row->m_total_weight); + case PollTableModel::VotePercentAVW: + return QString::number(row->m_vote_percent_AVW, 'f', 4); + case PollTableModel::Validated: + return row->m_validated; + case PollTableModel::TopAnswer: + return row->m_top_answer; + case PollTableModel::StaleResults: + return row->m_stale; + } // no default case, so the compiler can warn about missing cases + assert(false); + + case Qt::TextAlignmentRole: + switch (index.column()) { + case PollTableModel::Duration: + // Pass-through case + case PollTableModel::TotalVotes: + // Pass-through case + case PollTableModel::TotalWeight: + // Pass-through case + case PollTableModel::VotePercentAVW: + // Pass-through case + case PollTableModel::Validated: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + } + break; + + case PollTableModel::SortRole: + switch (index.column()) { + case PollTableModel::Title: + return row->m_title; + case PollTableModel::PollType: + return row->m_type_str; + case PollTableModel::Duration: + return row->m_duration; + case PollTableModel::Expiration: + return row->m_expiration; + case PollTableModel::WeightType: + return row->m_weight_type_str; + case PollTableModel::TotalVotes: + return row->m_total_votes; + case PollTableModel::TotalWeight: + return QVariant::fromValue(row->m_total_weight); + case PollTableModel::VotePercentAVW: + return QVariant::fromValue(row->m_vote_percent_AVW); + case PollTableModel::Validated: + return row->m_validated; + case PollTableModel::TopAnswer: + return row->m_top_answer; + case PollTableModel::StaleResults: + return row->m_stale; + } // no default case, so the compiler can warn about missing cases + assert(false); } - QModelIndex index(int row, int column, const QModelIndex &parent) const override - { - Q_UNUSED(parent); + return QVariant(); +} - if (row > static_cast(m_rows.size())) { - return QModelIndex(); +QVariant PollTableDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole && section < m_columns.size()) { + return m_columns[section]; } + } + + return QVariant(); +} - void* data = static_cast(const_cast(&m_rows[row])); +QModelIndex PollTableDataModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); - return createIndex(row, column, data); + if (row > static_cast(m_rows.size())) { + return QModelIndex(); } - Qt::ItemFlags flags(const QModelIndex &index) const override - { - if (!index.isValid()) { - return Qt::NoItemFlags; - } + void* data = static_cast(const_cast(&m_rows[row])); - return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); + return createIndex(row, column, data); +} + +Qt::ItemFlags PollTableDataModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; } - void reload(std::vector rows) - { - emit layoutAboutToBeChanged(); - m_rows = std::move(rows); - emit layoutChanged(); + return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + +void PollTableDataModel::reload(std::vector rows) +{ + emit layoutAboutToBeChanged(); + m_rows = std::move(rows); + emit layoutChanged(); +} + +void PollTableDataModel::handlePollStaleFlag(QString poll_txid_string) +{ + emit layoutAboutToBeChanged(); + + for (auto& iter : m_rows) { + if (iter.m_id == poll_txid_string) { + iter.m_stale = true; + } } -private: - QStringList m_columns; - std::vector m_rows; -}; // PollTableDataModel + emit layoutChanged(); +} } // Anonymous namespace // ----------------------------------------------------------------------------- @@ -203,6 +213,10 @@ PollTableModel::~PollTableModel() void PollTableModel::setModel(VotingModel* model) { m_model = model; + + // Connect poll stale handler to newVoteReceived signal from voting model, which propagates + // from the core. + connect(m_model, &VotingModel::newVoteReceived, this, &PollTableModel::handlePollStaleFlag); } void PollTableModel::setPollFilterFlags(PollFilterFlag flags) @@ -252,6 +266,11 @@ void PollTableModel::refresh() }); } +void PollTableModel::handlePollStaleFlag(QString poll_txid_string) +{ + m_data_model->handlePollStaleFlag(poll_txid_string); +} + void PollTableModel::changeTitleFilter(const QString& pattern) { emit layoutAboutToBeChanged(); diff --git a/src/qt/voting/polltablemodel.h b/src/qt/voting/polltablemodel.h index 0923c97a3d..38620d3bab 100644 --- a/src/qt/voting/polltablemodel.h +++ b/src/qt/voting/polltablemodel.h @@ -5,6 +5,7 @@ #ifndef GRIDCOIN_QT_VOTING_POLLTABLEMODEL_H #define GRIDCOIN_QT_VOTING_POLLTABLEMODEL_H +#include "uint256.h" #include "gridcoin/voting/filter.h" #include @@ -14,6 +15,28 @@ class PollItem; class VotingModel; +namespace { +class PollTableDataModel : public QAbstractTableModel +{ +public: + PollTableDataModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void reload(std::vector rows); + void handlePollStaleFlag(QString poll_txid_string); + +private: + QStringList m_columns; + std::vector m_rows; + +}; +} // Anonymous namespace + class PollTableModel : public QSortFilterProxyModel { Q_OBJECT @@ -31,6 +54,7 @@ class PollTableModel : public QSortFilterProxyModel VotePercentAVW, Validated, TopAnswer, + StaleResults }; enum Roles @@ -55,9 +79,11 @@ public slots: void changeTitleFilter(const QString& pattern); Qt::SortOrder sort(int column); + void handlePollStaleFlag(QString poll_txid_string); + private: VotingModel* m_model; - std::unique_ptr m_data_model; + std::unique_ptr m_data_model; GRC::PollFilterFlag m_filter_flags; QMutex m_refresh_mutex; }; diff --git a/src/qt/voting/votingmodel.cpp b/src/qt/voting/votingmodel.cpp index bc4ba9ff84..769374f64d 100644 --- a/src/qt/voting/votingmodel.cpp +++ b/src/qt/voting/votingmodel.cpp @@ -23,7 +23,6 @@ #include - using namespace GRC; using LogFlags = BCLog::LogFlags; @@ -36,10 +35,19 @@ namespace { //! void NewPollReceived(VotingModel* model, int64_t poll_time) { - LogPrint(LogFlags::QT, "GUI: received NewPollReceived() core signal"); + LogPrint(LogFlags::QT, "INFO: %s: received NewPollReceived() core signal", __func__); QMetaObject::invokeMethod(model, "handleNewPoll", Qt::QueuedConnection, - Q_ARG(int64_t, poll_time)); + Q_ARG(int64_t, poll_time)); +} + +void NewVoteReceived(VotingModel* model, uint256 poll_txid) +{ + LogPrint(LogFlags::QT, "INFO: %s: received NewVoteReceived() core signal", __func__); + + // Ugly but uint256 is not registered as a Metatype. + QMetaObject::invokeMethod(model, "handleNewVote", Qt::QueuedConnection, + Q_ARG(QString, QString().fromStdString(poll_txid.ToString()))); } std::optional BuildPollItem(const PollRegistry::Sequence::Iterator& iter) @@ -116,6 +124,9 @@ std::optional BuildPollItem(const PollRegistry::Sequence::Iterator& it item.m_top_answer = QString::fromStdString(result->WinnerLabel()).replace("_", " "); } + // Mark stale flag false since we just rebuilt the item. + item.m_stale = false; + g_timer.GetTimes(std::string{"End "} + std::string{__func__}, "buildPollTable"); return item; } @@ -134,6 +145,7 @@ VotingModel::VotingModel( , m_options_model(options_model) , m_wallet_model(wallet_model) , m_last_poll_time(0) + , m_pollitems() { subscribeToCoreSignals(); @@ -239,7 +251,7 @@ QStringList VotingModel::getActiveProjectUrls() const } -std::vector VotingModel::buildPollTable(const PollFilterFlag flags) const +std::vector VotingModel::buildPollTable(const PollFilterFlag flags) { g_timer.InitTimer(__func__, LogInstance().WillLogCategory(BCLog::LogFlags::VOTE)); g_timer.GetTimes(std::string{"Begin "} + std::string{__func__}, __func__); @@ -254,6 +266,24 @@ std::vector VotingModel::buildPollTable(const PollFilterFlag flags) co for (unsigned int i = 0; i < 3; ++i) { for (const auto& iter : WITH_LOCK(m_registry.cs_poll_registry, return m_registry.Polls().Where(flags))) { + // First check to see if the poll item already exists, and if so is it stale (i.e. a new vote has + // been received for that poll). If it is stale, it will need rebuilding. If not, we insert the cached + // poll item into the results and move on. + + bool pollitem_needs_rebuild = true; + auto pollitems_iter = m_pollitems.find(iter->Ref().Txid()); + + // Note that the NewVoteReceived core signal will also be fired during reorgs where votes are reverted, + // i.e. unreceived. This will cause the stale flag to be set on polls during reorg where votes have been + // removed during reorg, which is what is desired. + if (pollitems_iter != m_pollitems.end()) { + if (!pollitems_iter->second.m_stale) { + // Not stale... the cache entry is good. Insert into items to return and go to the next one. + items.push_back(pollitems_iter->second); + pollitem_needs_rebuild = false; + } + } + // Note that we are implementing a coarse-grained fork/rollback detector here. // We do this because we have eliminated the cs_main lock to free up the GUI. // Instead we have reversed the locking scheme and have the contract actions (add/delete) @@ -268,13 +298,18 @@ std::vector VotingModel::buildPollTable(const PollFilterFlag flags) co // Transactions that have not been rolled back by a reorg can be safely accessed for reading // by another thread as we are doing here. - try { - if (std::optional item = BuildPollItem(iter)) { - items.push_back(std::move(*item)); + if (pollitem_needs_rebuild) { + try { + if (std::optional item = BuildPollItem(iter)) { + // This will replace any stale existing entry in the cache with the freshly built item. + // It will also correctly add a new entry for a new item. + m_pollitems[iter->Ref().Txid()] = *item; + items.push_back(std::move(*item)); + } + } catch (InvalidDuetoReorgFork& e) { + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: Invalidated due to reorg/fork. Starting over.", + __func__); } - } catch (InvalidDuetoReorgFork& e) { - LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: Invalidated due to reorg/fork. Starting over.", - __func__); } // This must be AFTER BuildPollItem. If a reorg occurred during reg traversal that could invalidate @@ -440,6 +475,7 @@ VotingResult VotingModel::sendVote( void VotingModel::subscribeToCoreSignals() { uiInterface.NewPollReceived_connect(std::bind(NewPollReceived, this, std::placeholders::_1)); + uiInterface.NewVoteReceived_connect(std::bind(NewVoteReceived, this, std::placeholders::_1)); } void VotingModel::unsubscribeFromCoreSignals() @@ -457,6 +493,22 @@ void VotingModel::handleNewPoll(int64_t poll_time) emit newPollReceived(); } +void VotingModel::handleNewVote(QString poll_txid_string) +{ + uint256 poll_txid; + + poll_txid.SetHex(poll_txid_string.toStdString()); + + auto pollitems_iter = m_pollitems.find(poll_txid); + + if (pollitems_iter != m_pollitems.end()) { + // Set stale flag on poll item associated with vote. + pollitems_iter->second.m_stale = true; + } + + emit newVoteReceived(poll_txid_string); +} + // ----------------------------------------------------------------------------- // Class: AdditionalFieldEntry // ----------------------------------------------------------------------------- diff --git a/src/qt/voting/votingmodel.h b/src/qt/voting/votingmodel.h index 1afee6e127..68896d44c0 100644 --- a/src/qt/voting/votingmodel.h +++ b/src/qt/voting/votingmodel.h @@ -86,6 +86,8 @@ class PollItem std::vector m_choices; bool m_self_voted; GRC::PollResult::VoteDetail m_self_vote_detail; + + bool m_stale = true; }; //! @@ -132,7 +134,7 @@ class VotingModel : public QObject QString getCurrentPollTitle() const; QStringList getActiveProjectNames() const; QStringList getActiveProjectUrls() const; - std::vector buildPollTable(const GRC::PollFilterFlag flags) const; + std::vector buildPollTable(const GRC::PollFilterFlag flags); CAmount estimatePollFee() const; @@ -153,6 +155,7 @@ class VotingModel : public QObject signals: void newPollReceived(); + void newVoteReceived(QString poll_txid_string); private: GRC::PollRegistry& m_registry; @@ -161,11 +164,22 @@ class VotingModel : public QObject WalletModel& m_wallet_model; int64_t m_last_poll_time; + //! + //! \brief m_pollitems. A cache of poll items associated with the polls in the registry. + //! Each entry in the cache has a stale flag which is set when vote activity occurs, and is + //! defaulted to true (in construction) when the item is rebuilt by BuildPollItem inBuildPollTable, + //! then changed to false when BuildPollItem completes. When a vote is received (or "un" received + //! in a reorg situation), the NewVoteReceived signal from the core will cause the stale flag + //! in the appropriate corresponding poll item in this cache to be changed back to true. + //! + std::map m_pollitems; + void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); private slots: void handleNewPoll(int64_t poll_time); + void handleNewVote(QString poll_txid_string); }; // VotingModel #endif // GRIDCOIN_QT_VOTING_VOTINGMODEL_H