diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b778f58fa..7806832290 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [5.2.1.0] 2021-03-07, leisure +### Added + - voting: Add wait warning to voting tab loading message #2039 (@cyrossignol) + - rpc: Adds transaction hash and fees paid to consolidateunspent #2040 (@jamescowens) + +### Changed +- gui, voting: Make some minor adjustments for VotingDialog flow #2041 (@jamescowens) + +### Fixed + - beacon, util, gui: Fix small error in beacon db for renewals and fix snapshot download functionality #2036 (@jamescowens) + ## [5.2.0.0] 2021-03-01, mandatory, "Hilda" ### Added - gui: Add RAC column to wizard summary page projects table #1951 (@cyrossignol) diff --git a/configure.ac b/configure.ac index d6eb9d7bc2..70bfb932d8 100755 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 5) define(_CLIENT_VERSION_MINOR, 2) -define(_CLIENT_VERSION_REVISION, 0) +define(_CLIENT_VERSION_REVISION, 1) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2021) diff --git a/src/gridcoin/accrual/snapshot.h b/src/gridcoin/accrual/snapshot.h index 04a473d6ea..c11c45b8e3 100644 --- a/src/gridcoin/accrual/snapshot.h +++ b/src/gridcoin/accrual/snapshot.h @@ -1456,6 +1456,11 @@ class AccrualSnapshotRepository return m_registry.Deregister(height); } + bool CloseRegistryFile() + { + return m_registry.Close(); + } + private: AccrualSnapshotRegistry m_registry; //!< Tracks snapshot files state. }; // AccrualSnapshotRepository diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index 33d6b22e51..f359a6856e 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -367,7 +367,15 @@ bool BeaconRegistry::TryRenewal(Beacon_ptr& current_beacon_ptr, int& height, con renewal.m_prev_beacon_hash = current_beacon_ptr->m_hash; // Put the renewal beacon into the db. - m_beacon_db.update(renewal.m_hash, height, renewal); + if (!m_beacon_db.insert(renewal.m_hash, height, renewal)) + { + LogPrint(LogFlags::BEACON, "WARNING: %s: In renewal of beacon for cpid %s, address %s, hash %s, beacon db record " + "already exists.", + __func__, + renewal.m_cpid.ToString(), + renewal.GetAddress().ToString(), + renewal.m_hash.GetHex()); + } // Get the iterator to the renewal beacon. auto renewal_iter = m_beacon_db.find(renewal.m_hash); @@ -989,6 +997,35 @@ int BeaconRegistry::BeaconDB::Initialize(PendingBeaconMap& m_pending, BeaconMap& { bool status = true; int height = 0; + uint32_t version = 0; + + // First load the beacon db version from leveldb and check it against the constant in the class. + { + CTxDB txdb("r"); + + std::pair key = std::make_pair("beacon_db", "version"); + + bool status = txdb.ReadGenericSerializable(key, version); + + if (!status) version = 0; + } + + if (version != CURRENT_VERSION) + { + LogPrint(LogFlags::BEACON, "WARNING: %s: Version level of the beacon db stored in leveldb, %u, does not " + "match that required in this code level, version %u. Clearing the leveldb beacon " + "storage and setting version level to match this code level.", + __func__, + version, + CURRENT_VERSION); + + clear_leveldb(); + + LogPrint(LogFlags::BEACON, "INFO: %s: Leveldb beacon area cleared. Version level set to %u.", + __func__, + CURRENT_VERSION); + } + // If LoadDBHeight not successful or height is zero then leveldb has not been initialized before. // LoadDBHeight will also set the private member variable m_height_stored from leveldb for this first call. @@ -1175,6 +1212,9 @@ void BeaconRegistry::ResetInMemoryOnly() m_beacon_db.clear_in_memory_only(); } +// Required to make the linker happy. +constexpr uint32_t BeaconRegistry::BeaconDB::CURRENT_VERSION; + void BeaconRegistry::BeaconDB::clear_in_memory_only() { m_historical.clear(); @@ -1189,15 +1229,19 @@ bool BeaconRegistry::BeaconDB::clear_leveldb() CTxDB txdb("rw"); - std::string key_type = "beacon_db"; + std::string key_type = "beacon"; + uint256 start_key_hint_beacon = uint256(); + + status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_beacon); + + key_type = "beacon_db"; std::string start_key_hint_beacon_db {}; status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_beacon_db); - key_type = "beacon"; - uint256 start_key_hint_beacon = uint256(); - - status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_beacon); + // We want to write back into leveldb the revision level of the db in the running code. + std::pair key = std::make_pair(key_type, "version"); + status &= txdb.WriteGenericSerializable(key, CURRENT_VERSION); m_height_stored = 0; m_recnum_stored = 0; diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index 7aeb68b8d3..62a43b0b10 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -707,6 +707,15 @@ class BeaconRegistry : public IContractHandler class BeaconDB { public: + //! + //! \brief Version number of the beacon db. + //! + //! CONSENSUS: Increment this value when introducing a breaking change to the beacon db. This + //! will ensure that when the wallet is restarted, the level db beacon storage will be cleared and + //! reloaded from the contract replay with the correct lookback scope. + //! + static constexpr uint32_t CURRENT_VERSION = 1; + //! //! \brief Initializes the Beacon Registry map structures from the replay of the beacon states stored //! in the beacon database. diff --git a/src/gridcoin/contract/contract.cpp b/src/gridcoin/contract/contract.cpp index 7d66b52109..e7a592fcbc 100644 --- a/src/gridcoin/contract/contract.cpp +++ b/src/gridcoin/contract/contract.cpp @@ -463,8 +463,7 @@ void GRC::ReplayContracts(const CBlockIndex* pindex_end, const CBlockIndex* pind // Only apply activations that have not already been stored/loaded into // the beacon DB. This is at the block level, so we have to be careful here. // If the pindex->nHeight is equal to the beacon_db_height, then the ActivatePending - // has already been replayed. This is different than below in ApplyContracts, where - // there can be more than one contract in a block, so the condition is subtly different. + // has already been replayed. if (pindex->nHeight > beacon_db_height) { GetBeaconRegistry().ActivatePending( @@ -510,9 +509,8 @@ void GRC::ApplyContracts( { for (const auto& contract : tx.GetContracts()) { // Do not (re)apply contracts that have already been stored/loeaded into - // the beacon DB. Note here if pindex->nHeight == beacon_db_height, the contract - // will be replayed. This is because there can be more than one contract per block. - if ((pindex->nHeight < beacon_db_height) && contract.m_type == ContractType::BEACON) + // the beacon DB. + if ((pindex->nHeight <= beacon_db_height) && contract.m_type == ContractType::BEACON) { LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: ApplyContract tx skipped: " "pindex->height = %i <= beacon_db_height = %i and " diff --git a/src/gridcoin/gridcoin.cpp b/src/gridcoin/gridcoin.cpp index a75b25dcf5..e928bdb60e 100644 --- a/src/gridcoin/gridcoin.cpp +++ b/src/gridcoin/gridcoin.cpp @@ -398,6 +398,11 @@ bool GRC::Initialize(ThreadHandlerPtr threads, CBlockIndex* pindexBest) return true; } +void GRC::CloseResearcherRegistryFile() +{ + Tally::CloseRegistryFile(); +} + void GRC::ScheduleBackgroundJobs(CScheduler& scheduler) { scheduler.schedule(CheckBlockIndexJob); diff --git a/src/gridcoin/gridcoin.h b/src/gridcoin/gridcoin.h index 3f18141926..8bf914d7eb 100644 --- a/src/gridcoin/gridcoin.h +++ b/src/gridcoin/gridcoin.h @@ -18,6 +18,12 @@ namespace GRC { //! bool Initialize(ThreadHandlerPtr threads, CBlockIndex* pindexBest); +//! +//! \brief This closes the underlying research file to support the snapshot update +//! process, which must remove the accrual directory as part of the blockchain cleanup. +//! +void CloseResearcherRegistryFile(); + //! //! \brief Set up Gridcoin-specific background jobs. //! diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 219fd42f8b..be7cbc7321 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -792,6 +792,11 @@ class ResearcherTally return Initialize(GetStartHeight(), SuperblockPtr::Empty()); } + + bool CloseRegistryFile() + { + return m_snapshots.CloseRegistryFile(); + } }; // ResearcherTally ResearcherTally g_researcher_tally; //!< Tracks lifetime research rewards. @@ -1261,3 +1266,8 @@ const CBlockIndex* Tally::GetBaseline() { return g_researcher_tally.GetBaseline(); } + +void Tally::CloseRegistryFile() +{ + g_researcher_tally.CloseRegistryFile(); +} diff --git a/src/gridcoin/tally.h b/src/gridcoin/tally.h index f0fb4a743f..f8100c51fd 100644 --- a/src/gridcoin/tally.h +++ b/src/gridcoin/tally.h @@ -261,5 +261,13 @@ class Tally //! const static CBlockIndex* GetBaseline(); + //! + //! \brief This closes the underlying register file of the researcher repository. It + //! is ONLY used in Shutdown() to release the lock on the registry.dat file + //! so that a snapshot download process cleanup will succeed, since the accrual directory + //! needs to be removed. + //! + static void CloseRegistryFile(); + }; } diff --git a/src/gridcoin/upgrade.cpp b/src/gridcoin/upgrade.cpp index 133a194ec7..ab1aae467e 100644 --- a/src/gridcoin/upgrade.cpp +++ b/src/gridcoin/upgrade.cpp @@ -376,6 +376,7 @@ bool Upgrade::CleanupBlockchainData() if (!fs::remove_all(*Iter)) return false; } } + continue; } @@ -391,6 +392,7 @@ bool Upgrade::CleanupBlockchainData() if (!fs::remove(*Iter)) return false; } + continue; } } diff --git a/src/init.cpp b/src/init.cpp index f02924fd1a..8cbc9d8346 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -104,6 +104,11 @@ void Shutdown(void* parg) StopNode(); bitdb.Flush(true); StopRPCThreads(); + + // This is necessary here to prevent a snapshot download from failing at the cleanup + // step because of a write lock on accrual/registry.dat. + GRC::CloseResearcherRegistryFile(); + fs::remove(GetPidFile()); UnregisterWallet(pwalletMain); delete pwalletMain; diff --git a/src/qt/aboutdialog.cpp b/src/qt/aboutdialog.cpp index 9388e02d81..e27fbd56bf 100755 --- a/src/qt/aboutdialog.cpp +++ b/src/qt/aboutdialog.cpp @@ -7,7 +7,7 @@ AboutDialog::AboutDialog(QWidget *parent) : ui(new Ui::AboutDialog) { ui->setupUi(this); - ui->copyrightLabel->setText("Copyright 2009-2020 The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"); + ui->copyrightLabel->setText("Copyright 2009-2021 The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"); } void AboutDialog::setModel(ClientModel *model) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index ffb4c06ec7..30ffb84997 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -23,6 +23,7 @@ #include "util.h" #include "winshutdownmonitor.h" #include "gridcoin/upgrade.h" +#include "gridcoin/gridcoin.h" #include "upgradeqt.h" #include @@ -404,7 +405,7 @@ int main(int argc, char *argv[]) // CTxDB().Close(); - if (Snapshot.SnapshotMain()) + if (Snapshot.SnapshotMain(app)) LogPrintf("Snapshot: Success!"); else @@ -560,6 +561,7 @@ int StartGridcoinQt(int argc, char *argv[], QApplication& app, OptionsModel& opt threads->removeAll(); threads.reset(); + return EXIT_SUCCESS; } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index a0179b2455..f852cbb30e 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1136,7 +1136,7 @@ void BitcoinGUI::gotoSendCoinsPage() void BitcoinGUI::gotoVotingPage() { votingAction->setChecked(true); - votingPage->resetData(); + //votingPage->loadPolls(false); centralWidget->setCurrentWidget(votingPage); exportAction->setEnabled(false); diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index c6163d4143..53d3213c49 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -108,7 +108,7 @@ PeerTableModel::PeerTableModel(ClientModel *parent) : clientModel(parent), timer(nullptr) { - columns << tr("NodeId") << tr("Node/Service") << tr("Ping") << tr("Sent") << tr("Received") << tr("User Agent"); + columns << tr("Node ID") << tr("Node/Service") << tr("Ping") << tr("Sent") << tr("Received") << tr("User Agent"); priv.reset(new PeerTablePriv()); // set up timer for auto refresh diff --git a/src/qt/upgradeqt.cpp b/src/qt/upgradeqt.cpp index 851481391c..ffee89620e 100644 --- a/src/qt/upgradeqt.cpp +++ b/src/qt/upgradeqt.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -23,16 +22,8 @@ QString UpgradeQt::ToQString(const std::string& string) return QString::fromStdString(string); } -bool UpgradeQt::SnapshotMain() +bool UpgradeQt::SnapshotMain(QApplication& SnapshotApp) { - // Keep this separate from the main application - int xargc = 1; - char *xargv[] = {(char*)"gridcoinresearch"}; - - QApplication SnapshotApp(xargc, xargv); - - SnapshotApp.setOrganizationName("Gridcoin"); - SnapshotApp.setApplicationName("Gridcoin-Qt"); SnapshotApp.processEvents(); SnapshotApp.setWindowIcon(QPixmap(":/images/gridcoin")); diff --git a/src/qt/upgradeqt.h b/src/qt/upgradeqt.h index 1561c5de39..24220a689e 100644 --- a/src/qt/upgradeqt.h +++ b/src/qt/upgradeqt.h @@ -7,6 +7,7 @@ #include #include +#include class UpgradeQt { @@ -20,7 +21,7 @@ class UpgradeQt //! //! \return Returns success of snapshot task. //! - bool SnapshotMain(); + bool SnapshotMain(QApplication& SnapshotApp); //! //! \brief Function called via thread to download snapshot and provide realtime updates of progress. //! diff --git a/src/qt/votingdialog.cpp b/src/qt/votingdialog.cpp index 3ce99ddbd6..ef1507c1ac 100644 --- a/src/qt/votingdialog.cpp +++ b/src/qt/votingdialog.cpp @@ -278,6 +278,9 @@ VotingItem* BuildPollItem(const PollRegistry::Sequence::Iterator& iter) void VotingTableModel::resetData(bool history) { + std::string function = __func__; + function += ": "; + // data: erase if (data_.size()) { beginRemoveRows(QModelIndex(), 0, data_.size() - 1); @@ -288,6 +291,8 @@ void VotingTableModel::resetData(bool history) endRemoveRows(); } + g_timer.GetTimes(function + "erase data", "votedialog"); + // retrieve data std::vector items; @@ -298,8 +303,11 @@ void VotingTableModel::resetData(bool history) if (VotingItem* item = BuildPollItem(iter)) { item->rowNumber_ = items.size() + 1; items.push_back(item); + } } + + g_timer.GetTimes(function + "populate poll results (cs_main lock)", "votedialog"); } // data: populate @@ -308,6 +316,8 @@ void VotingTableModel::resetData(bool history) for(size_t i=0; i < items.size(); i++) data_.append(items[i]); endInsertRows(); + + g_timer.GetTimes(function + "insert data in display table", "votedialog"); } } @@ -427,11 +437,19 @@ VotingDialog::VotingDialog(QWidget *parent) watcher.setProperty("running", false); connect(&watcher, SIGNAL(finished()), this, SLOT(onLoadingFinished())); loadingIndicator = new QLabel(this); - loadingIndicator->move(50,170); + loadingIndicator->setWordWrap(true); + + groupboxvlayout->addWidget(loadingIndicator); chartDialog_ = new VotingChartDialog(this); voteDialog_ = new VotingVoteDialog(this); pollDialog_ = new NewPollDialog(this); + + loadingIndicator->setText(tr("Press reload to load polls... This can take several minutes, and the wallet may not respond until finished.")); + tableView_->hide(); + loadingIndicator->show(); + + QObject::connect(vote_update_age_timer, SIGNAL(timeout()), this, SLOT(setStale())); } void VotingDialog::setModel(WalletModel *wallet_model) @@ -446,12 +464,23 @@ void VotingDialog::setModel(WalletModel *wallet_model) void VotingDialog::loadPolls(bool history) { + std::string function = __func__; + function += ": "; + bool isRunning = watcher.property("running").toBool(); if (tableModel_&& !isRunning) { - loadingIndicator->setText(tr("...loading data!")); + loadingIndicator->setText(tr("Recalculating voting weights... This can take several minutes, and the wallet may not respond until finished.")); + tableView_->hide(); loadingIndicator->show(); + + g_timer.InitTimer("votedialog", LogInstance().WillLogCategory(BCLog::LogFlags::MISC)); + vote_update_age_timer->start(STALE); + QFuture future = QtConcurrent::run(tableModel_, &VotingTableModel::resetData, history); + + g_timer.GetTimes(function + "Post future assignment", "votedialog"); + watcher.setProperty("running", true); watcher.setFuture(future); } @@ -467,6 +496,26 @@ void VotingDialog::loadHistory(void) loadPolls(true); } +void VotingDialog::setStale(void) +{ + LogPrint(BCLog::LogFlags::MISC, "INFO: %s called.", __func__); + + // If the stale flag is not set, but this function is called, it is from the timeout + // trigger of the vote_update_age_timer. Therefore set the loading indicator to stale + // and set the stale flag to true. The stale flag will be reset and the timer restarted + // when the loadPolls is called to refresh. + if (!stale) + { + loadingIndicator->setText(tr("Poll data is more than one hour old. Press reload to update... " + "This can take several minutes, and the wallet may not respond " + "until finished.")); + tableView_->hide(); + loadingIndicator->show(); + + stale = true; + } +} + void VotingDialog::onLoadingFinished(void) { watcher.setProperty("running", false); @@ -474,9 +523,12 @@ void VotingDialog::onLoadingFinished(void) int rowsCount = tableView_->verticalHeader()->count(); if (rowsCount > 0) { loadingIndicator->hide(); + tableView_->show(); } else { loadingIndicator->setText(tr("No polls !")); } + + stale = false; } void VotingDialog::tableColResize(void) diff --git a/src/qt/votingdialog.h b/src/qt/votingdialog.h index f4e369a015..ee75743477 100644 --- a/src/qt/votingdialog.h +++ b/src/qt/votingdialog.h @@ -164,6 +164,9 @@ class VotingDialog void setModel(WalletModel *wallet_model); private: + // The number of milliseconds of age at which the poll data is stale. Currently one hour equivalent. + static constexpr int64_t STALE = 60 * 60 * 1000; + QLineEdit *filterTQAU; QPushButton *resetButton; QPushButton *histButton; @@ -176,6 +179,8 @@ class VotingDialog NewPollDialog *pollDialog_; QLabel *loadingIndicator; QFutureWatcher watcher; + QTimer* vote_update_age_timer = new QTimer(this); + bool stale = false; private: virtual void showEvent(QShowEvent *); @@ -185,6 +190,7 @@ class VotingDialog private slots: void onLoadingFinished(void); + void setStale(void); public slots: void filterTQAUChanged(const QString &); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 804d23ea8a..1e638fe8bb 100755 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -797,8 +797,10 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) " here."); result.pushKV("result", true); - result.pushKV("UTXOs consolidated", (uint64_t) iInputCount); - result.pushKV("Output UTXO value", (double)(nAmount - nFeeRequired) / COIN); + result.pushKV("utxos_consolidated", (uint64_t) iInputCount); + result.pushKV("output_utxo_value", FormatMoney(nAmount - nFeeRequired)); + result.pushKV("fee", FormatMoney(nFeeRequired)); + result.pushKV("txid", wtxNew.GetHash().GetHex()); return result; }