diff --git a/src/gridcoin/voting/result.cpp b/src/gridcoin/voting/result.cpp index 1cdd746eff..98a8b72dee 100644 --- a/src/gridcoin/voting/result.cpp +++ b/src/gridcoin/voting/result.cpp @@ -864,18 +864,38 @@ class VoteCounter { CTransaction tx; - if (!m_txdb.ReadDiskTx(txid, tx)) { - LogPrint(LogFlags::VOTE, "%s: failed to read vote tx", __func__); + bool read_tx_success = false; + + // This is very ugly. In testing for implement poll expiration reminders PR2716, there is an issue with ReadDiskTx + // on very fast machines, where upon receipt of a vote on an existing poll, the poll builder tests for the transaction + // BEFORE it is committed to disk. This retry loop is essentially zero overhead for an immediate success, but does + // up to 10 tries over up to 2.5 seconds total to "wait" for the transaction to appear in leveldb. + for (unsigned int i = 0; i < 10; ++i) { + if (m_txdb.ReadDiskTx(txid, tx)) { + read_tx_success = true; + break; + } else { + LogPrintf("WARN: %s: failed to read vote tx in try %u", __func__, i + 1); + } + + if (!MilliSleep(250)) { + // Interrupt with throw if MilliSleep interrupted by op sys signal. + throw InvalidVoteError(); + } + } + + if (!read_tx_success) { + LogPrintf("WARN: %s: failed to read vote tx after 10 tries", __func__); throw InvalidVoteError(); } if (tx.nTime < m_poll.m_timestamp) { - LogPrint(LogFlags::VOTE, "%s: tx earlier than poll", __func__); + LogPrintf("WARN: %s: tx earlier than poll", __func__); throw InvalidVoteError(); } if (m_poll.Expired(tx.nTime)) { - LogPrint(LogFlags::VOTE, "%s: tx exceeds expiration", __func__); + LogPrintf("WARN: %s: tx exceeds expiration", __func__); throw InvalidVoteError(); } @@ -885,7 +905,7 @@ class VoteCounter } if (!contract.WellFormed()) { - LogPrint(LogFlags::VOTE, "%s: skipped bad contract", __func__); + LogPrintf("WARN: %s: skipped bad contract", __func__); continue; } @@ -1259,6 +1279,15 @@ VoteDetail::VoteDetail() : m_amount(0), m_magnitude(Magnitude::Zero()), m_ismine { } +VoteDetail::VoteDetail(const VoteDetail &original_votedetail) + : m_amount(original_votedetail.m_amount) + , m_mining_id(original_votedetail.m_mining_id) + , m_magnitude(original_votedetail.m_magnitude) + , m_ismine(original_votedetail.m_ismine) + , m_responses(original_votedetail.m_responses) +{ +} + bool VoteDetail::Empty() const { return m_amount == 0 && m_magnitude == 0; diff --git a/src/gridcoin/voting/result.h b/src/gridcoin/voting/result.h index 758a7bbcf9..5ca0cda601 100644 --- a/src/gridcoin/voting/result.h +++ b/src/gridcoin/voting/result.h @@ -71,6 +71,13 @@ class PollResult //! VoteDetail(); + //! + //! \brief User copy constructor. + //! + //! \param original_votedetail + //! + VoteDetail(const VoteDetail& original_votedetail); + //! //! \brief Determine whether a vote contributes no weight. //! diff --git a/src/qt/voting/polltab.cpp b/src/qt/voting/polltab.cpp index 10f5ffb83e..672ab759ac 100644 --- a/src/qt/voting/polltab.cpp +++ b/src/qt/voting/polltab.cpp @@ -153,6 +153,11 @@ PollTab::PollTab(QWidget* parent) connect(ui->table, &QAbstractItemView::doubleClicked, this, &PollTab::showPreferredDialog); connect(ui->table, &QWidget::customContextMenuRequested, this, &PollTab::showTableContextMenu); connect(m_polltable_model.get(), &PollTableModel::layoutChanged, this, &PollTab::finishRefresh); + + // Forward the polltable model signal to the Poll Tab signal to avoid having to directly include the PollTableModel + // in the voting page. + connect(m_polltable_model.get(), &PollTableModel::newVoteReceivedAndPollMarkedDirty, + this, &PollTab::newVoteReceivedAndPollMarkedDirty); } PollTab::~PollTab() diff --git a/src/qt/voting/polltab.h b/src/qt/voting/polltab.h index 8f333e0aad..0034f26682 100644 --- a/src/qt/voting/polltab.h +++ b/src/qt/voting/polltab.h @@ -50,6 +50,9 @@ class PollTab : public QWidget void setVotingModel(VotingModel* voting_model); void setPollFilterFlags(GRC::PollFilterFlag flags); +signals: + void newVoteReceivedAndPollMarkedDirty(); + public slots: void changeViewMode(const ViewId view_id); void refresh(); diff --git a/src/qt/voting/polltablemodel.cpp b/src/qt/voting/polltablemodel.cpp index 7a7c6d3f7e..e169ae8dbb 100644 --- a/src/qt/voting/polltablemodel.cpp +++ b/src/qt/voting/polltablemodel.cpp @@ -5,6 +5,7 @@ #include "qt/guiutil.h" #include "qt/voting/polltablemodel.h" #include "qt/voting/votingmodel.h" +#include "logging.h" #include #include @@ -255,6 +256,9 @@ const PollItem* PollTableModel::rowItem(int row) const void PollTableModel::refresh() { if (!m_voting_model || !m_refresh_mutex.tryLock()) { + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: m_refresh_mutex is already taken, so tryLock failed", + __func__); + return; } @@ -269,6 +273,8 @@ void PollTableModel::refresh() void PollTableModel::handlePollStaleFlag(QString poll_txid_string) { m_data_model->handlePollStaleFlag(poll_txid_string); + + emit newVoteReceivedAndPollMarkedDirty(); } void PollTableModel::changeTitleFilter(const QString& pattern) diff --git a/src/qt/voting/polltablemodel.h b/src/qt/voting/polltablemodel.h index 1c9d0269b0..5f3b1ac406 100644 --- a/src/qt/voting/polltablemodel.h +++ b/src/qt/voting/polltablemodel.h @@ -74,6 +74,9 @@ class PollTableModel : public QSortFilterProxyModel QString columnName(int offset) const; const PollItem* rowItem(int row) const; +signals: + void newVoteReceivedAndPollMarkedDirty(); + public slots: void refresh(); void changeTitleFilter(const QString& pattern); diff --git a/src/qt/voting/votingpage.cpp b/src/qt/voting/votingpage.cpp index 52ab4de4f4..dc2e22403e 100644 --- a/src/qt/voting/votingpage.cpp +++ b/src/qt/voting/votingpage.cpp @@ -132,12 +132,14 @@ void VotingPage::setVotingModel(VotingModel* model) // Now that PollItem caching is available, automatically refresh current poll tab on receipt of new poll or vote. connect(model, &VotingModel::newPollReceived, [this]() { ui->pollReceivedLabel->show(); - currentTab().refresh(); + getActiveTab()->refresh(); ui->pollReceivedLabel->hide(); }); - connect(model, &VotingModel::newVoteReceived, [this]() { - currentTab().refresh(); + // Using the newVoteReceivedAndPollMarkedDirty instead of newVoteReceived insures the poll staleness flag in the appropriate + // poll item has been marked dirty before the refresh is called. + connect(getActiveTab(), &PollTab::newVoteReceivedAndPollMarkedDirty, [this]() { + getActiveTab()->refresh(); }); }