diff --git a/.github/workflows/cmake-ci.yml b/.github/workflows/cmake-ci.yml index dd34e2d624..4105710601 100644 --- a/.github/workflows/cmake-ci.yml +++ b/.github/workflows/cmake-ci.yml @@ -131,7 +131,7 @@ jobs: qt@5 options: >- -DENABLE_GUI=ON - -DQt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 + -DQt5_DIR=/usr/local/opt/qt@5/lib/cmake/Qt5 -DENABLE_QRENCODE=ON -DENABLE_UPNP=ON - tag: system-libs diff --git a/configure.ac b/configure.ac index b9f1baed24..d1e2fcf553 100755 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 5) define(_CLIENT_VERSION_MINOR, 4) define(_CLIENT_VERSION_REVISION, 5) -define(_CLIENT_VERSION_BUILD, 5) +define(_CLIENT_VERSION_BUILD, 6) define(_CLIENT_VERSION_IS_RELEASE, false) define(_COPYRIGHT_YEAR, 2024) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 6e67219bec..ffc7dad3cb 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -106,6 +106,7 @@ QT_FORMS_UI = \ qt/forms/sendcoinsentry.ui \ qt/forms/signverifymessagedialog.ui \ qt/forms/transactiondescdialog.ui \ + qt/forms/updatedialog.ui \ qt/forms/voting/additionalfieldstableview.ui \ qt/forms/voting/pollcard.ui \ qt/forms/voting/pollcardview.ui \ @@ -171,6 +172,7 @@ QT_MOC_CPP = \ qt/moc_transactionfilterproxy.cpp \ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ + qt/moc_updatedialog.cpp \ qt/moc_walletmodel.cpp \ qt/researcher/moc_projecttablemodel.cpp \ qt/researcher/moc_researchermodel.cpp \ @@ -298,6 +300,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/transactionrecord.h \ qt/transactiontablemodel.h \ qt/transactionview.h \ + qt/updatedialog.h \ qt/upgradeqt.h \ qt/voting/additionalfieldstableview.h \ qt/voting/additionalfieldstablemodel.h \ @@ -388,6 +391,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ qt/upgradeqt.cpp \ + qt/updatedialog.cpp \ qt/voting/additionalfieldstableview.cpp \ qt/voting/additionalfieldstablemodel.cpp \ qt/voting/poll_types.cpp \ diff --git a/src/alert.cpp b/src/alert.cpp index 0ee441818a..b66293d36b 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -113,11 +113,11 @@ bool CAlert::Cancels(const CAlert& alert) const return (alert.nID <= nCancel || setCancel.count(alert.nID)); } -bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const +bool CAlert::AppliesTo(int version, std::string strSubVerIn) const { // TODO: rework for client-version-embedded-in-strSubVer ? return (IsInEffect() && - nMinVer <= nVersion && nVersion <= nMaxVer && + nMinVer <= version && version <= nMaxVer && (setSubVer.empty() || setSubVer.count(strSubVerIn))); } diff --git a/src/alert.h b/src/alert.h index 50636eca8a..c085de4081 100644 --- a/src/alert.h +++ b/src/alert.h @@ -94,7 +94,7 @@ class CAlert : public CUnsignedAlert uint256 GetHash() const; bool IsInEffect() const; bool Cancels(const CAlert& alert) const; - bool AppliesTo(int nVersion, std::string strSubVerIn) const; + bool AppliesTo(int version, std::string strSubVerIn) const; bool AppliesToMe() const; bool RelayTo(CNode* pnode) const; bool CheckSignature() const; diff --git a/src/chainparams.h b/src/chainparams.h index 16db13777a..908d217824 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -153,7 +153,7 @@ inline bool IsV12Enabled(int nHeight) return nHeight >= Params().GetConsensus().BlockV12Height; } -inline bool IsV13Enabled(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +inline bool IsV13Enabled(int nHeight) { // The argument driven override temporarily here to facilitate testing. diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index d0e70943bb..e529fdff07 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -171,7 +171,7 @@ bool Beacon::Expired(const int64_t now) const bool Beacon::Renewable(const int64_t now) const { - return Age(now) > RENEWAL_AGE; + return (!Expired(now) && Age(now) > RENEWAL_AGE); } bool Beacon::Renewed() const @@ -311,6 +311,11 @@ const BeaconRegistry::PendingBeaconMap& BeaconRegistry::PendingBeacons() const return m_pending; } +const std::set& BeaconRegistry::ExpiredBeacons() const +{ + return m_expired_pending; +} + BeaconOption BeaconRegistry::Try(const Cpid& cpid) const { const auto iter = m_beacons.find(cpid); @@ -355,9 +360,13 @@ std::vector BeaconRegistry::FindPending(const Cpid& cpid) const const BeaconOption BeaconRegistry::FindHistorical(const uint256& hash) { - BeaconOption beacon = m_beacon_db.find(hash)->second; + auto beacon_iter = m_beacon_db.find(hash); - return beacon; + if (beacon_iter != m_beacon_db.end()) { + return beacon_iter->second; + } + + return nullptr; } bool BeaconRegistry::ContainsActive(const Cpid& cpid, const int64_t now) const @@ -375,13 +384,13 @@ bool BeaconRegistry::ContainsActive(const Cpid& cpid) const } //! -//! \brief This resets the in-memory maps of the registry. It does NOT -//! clear the LevelDB storage. +//! \brief This resets the in-memory maps of the registry and the LevelDB backing storage. //! void BeaconRegistry::Reset() { m_beacons.clear(); m_pending.clear(); + m_expired_pending.clear(); m_beacon_db.clear(); } @@ -788,6 +797,144 @@ int BeaconRegistry::GetDBHeight() return height; } +Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, + std::shared_ptr>> beacon_chain_out) +{ + const auto ChainletErrorHandle = [this](unsigned int i, Beacon_ptr beacon, std::string error_message) { + error("%s: Beacon chainlet is corrupted at link %u for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," + "prev_beacon_ctx_hash = %s, status = %s: %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString(), + error_message); + + std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at link %u for cpid %s: timestamp = %s" + PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString(), + error_message); + + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"The beacon registry is corrupted and will be rebuilt on the next start. " + "Please restart."}); + }; + + const auto ChainletLinkLog = [&beacon_chain_out](unsigned int i, Beacon_ptr beacon) { + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: beacon chainlet link %u for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s, status = %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + if (beacon_chain_out == nullptr) { + return; + } + + beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + }; + + unsigned int i = 0; + + // Given that we have had rare situations where somehow circularity has occurred in the beacon chainlet, which either + // results in the current hash and previous hash being the same, or even suspected previous hash of another entry pointing + // back to a beacon in a circular manner, this vector is used to detect the circularity. + std::vector encountered_hashes; + + // The chain head itself. (This uses a scope to separate beacon_iter.) + { + auto beacon_iter = m_beacon_db.find(beacon->m_hash); + + if (beacon_iter == m_beacon_db.end()) { + // Beacon chainlet chainhead cannot be found. This is fatal. + ChainletErrorHandle(i, beacon, "not found in the registry"); + } + + // Make sure status is renewed or active. + if (beacon_iter->second->m_status != BeaconStatusForStorage::ACTIVE + && beacon_iter->second->m_status != BeaconStatusForStorage::RENEWAL) { + ChainletErrorHandle(i, beacon, "beacon status is not active or renewal"); + } + + encountered_hashes.push_back(beacon->m_hash); + + ChainletLinkLog(i, beacon); + + ++i; + } + + // Walk back the entries in the historical beacon map linked by renewal prev tx hash until the first + // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier + // than here. + while (beacon->Renewed()) + { + // Select previous beacon in chainlet + auto beacon_iter = m_beacon_db.find(beacon->m_previous_hash); + + if (beacon_iter == m_beacon_db.end()) { + ChainletErrorHandle(i, beacon, "previous beacon not found in the registry"); + } + + if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_previous_hash) != encountered_hashes.end()) { + // If circularity is found this is an indication of corruption of beacon state and is fatal. + // Produce an error message, reset the beacon registry, and require a restart of the node. + ChainletErrorHandle(i, beacon, "circularity encountered"); + } + + // Reassign previous beacon to beacon. + beacon = beacon_iter->second; + + encountered_hashes.push_back(beacon->m_hash); + + if (beacon_chain_out != nullptr) { + ChainletLinkLog(i, beacon); + } + + ++i; + } + + // Check of initial advertised beacon's previous hash. This should point to the pending beacon that was activated and not + // anywhere else. + { + // Select previous beacon in chainlet + auto beacon_iter = m_beacon_db.find(beacon->m_previous_hash); + + if (beacon_iter == m_beacon_db.end()) { + ChainletErrorHandle(i, beacon, "previous beacon not found in the registry"); + } + + // Make sure status of previous beacon is pending. + if (beacon_iter->second->m_status != BeaconStatusForStorage::PENDING) { + ChainletErrorHandle(i, beacon, "previous beacon to the beacon marked active is not pending"); + } + + if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_previous_hash) != encountered_hashes.end()) { + // If circularity is found this is an indication of corruption of beacon state and is fatal. + // Produce an error message, reset the beacon registry, and require a restart of the node. + ChainletErrorHandle(i, beacon, "circularity encountered"); + } + + // Note that we do not actually walk back to the pending beacon. The parameter beacon remains at the activated beacon. + } + + return beacon; +} + bool BeaconRegistry::NeedsIsContractCorrection() { return m_beacon_db.NeedsIsContractCorrection(); @@ -880,58 +1027,75 @@ void BeaconRegistry::ActivatePending( { LogPrint(LogFlags::BEACON, "INFO: %s: Called for superblock at height %i.", __func__, height); - // Activate the pending beacons that are not expired with respect to pending age. + // It is possible that more than one pending beacon with the same CPID can be attempted to be + // activated in the same superblock. The behavior here to agree with the original implementation + // is the last one. Here we are going to use a map keyed by the CPID with the array style insert + // to ensure that the LAST pending beacon verified is the one activated. + BeaconMap verified_beacons; + for (const auto& id : beacon_ids) { auto iter_pair = m_pending.find(id); if (iter_pair != m_pending.end()) { + bool already_found = (verified_beacons.find(iter_pair->second->m_cpid) != verified_beacons.end()); - Beacon_ptr found_pending_beacon = iter_pair->second; + if (already_found) { + LogPrint(LogFlags::BEACON, "INFO: %s: More than one pending beacon verified for the same CPID %s. Overriding previous" + "verified beacon.", + __func__, + iter_pair->second->m_cpid.ToString()); + } - // Create a new beacon to activate from the found pending beacon. - Beacon activated_beacon(*iter_pair->second); + verified_beacons[iter_pair->second->m_cpid] = iter_pair->second; + } + } - // Update the new beacon's prev hash to be the hash of the pending beacon that is being activated. - activated_beacon.m_previous_hash = found_pending_beacon->m_hash; + // Activate the pending beacons that are not expired with respect to pending age as of the time of verification (the + // committing of the superblock). + for (const auto& iter_pair : verified_beacons) { - // We are going to have to use a composite hash for these because activation is not done as - // individual transactions. Rather groups are done in each superblock under one hash. The - // hash of the block hash, and the pending beacon that is being activated's hash is sufficient. - activated_beacon.m_status = BeaconStatusForStorage::ACTIVE; + Beacon_ptr last_pending_beacon = iter_pair.second; - activated_beacon.m_hash = Hash(block_hash, found_pending_beacon->m_hash); + // Create a new beacon to activate from the found pending beacon. + Beacon activated_beacon(*iter_pair.second); - LogPrint(LogFlags::BEACON, "INFO: %s: Activating beacon for cpid %s, address %s, hash %s.", - __func__, - activated_beacon.m_cpid.ToString(), - activated_beacon.GetAddress().ToString(), - activated_beacon.m_hash.GetHex()); - - // It is possible that more than one pending beacon with the same CPID can be attempted to be - // activated in the same superblock. The behavior here to agree with the original implementation - // is the last one. So get rid of any previous one activated/inserted. - auto found_already_activated_beacon = m_beacon_db.find(activated_beacon.m_hash); - if (found_already_activated_beacon != m_beacon_db.end()) - { - m_beacon_db.erase(activated_beacon.m_hash); - } + // Update the new beacon's prev hash to be the hash of the pending beacon that is being activated. + activated_beacon.m_previous_hash = last_pending_beacon->m_hash; - m_beacon_db.insert(activated_beacon.m_hash, height, activated_beacon); + // We are going to have to use a composite hash for these because activation is not done as + // individual transactions. Rather groups are done in each superblock under one hash. The + // hash of the block hash, and the pending beacon that is being activated's hash is sufficient. + activated_beacon.m_status = BeaconStatusForStorage::ACTIVE; - // This is the subscript form of insert. Important here because an activated beacon should - // overwrite any existing entry in the m_beacons map. - m_beacons[activated_beacon.m_cpid] = m_beacon_db.find(activated_beacon.m_hash)->second; + activated_beacon.m_hash = Hash(block_hash, last_pending_beacon->m_hash); - // Remove the pending beacon entry from the pending map. (Note this entry still exists in the historical - // table and the db. - m_pending.erase(iter_pair); - } + LogPrint(LogFlags::BEACON, "INFO: %s: Activating beacon for cpid %s, address %s, hash %s.", + __func__, + activated_beacon.m_cpid.ToString(), + activated_beacon.GetAddress().ToString(), + activated_beacon.m_hash.GetHex()); + + m_beacon_db.insert(activated_beacon.m_hash, height, activated_beacon); + + // This is the subscript form of insert. Important here because an activated beacon should + // overwrite any existing entry in the m_beacons map. + m_beacons[activated_beacon.m_cpid] = m_beacon_db.find(activated_beacon.m_hash)->second; + + // Remove the pending beacon entry from the pending map. (Note this entry still exists in the historical + // table and the db. + m_pending.erase(iter_pair.second->GetId()); } - // Discard pending beacons that are expired with respect to pending age. + // Clear the expired pending beacon set. There is no need to retain expired beacons beyond one SB boundary (which is when + // this method is called) as this gives ~960 blocks of reorganization depth before running into the slight possibility that + // a different SB could verify a different pending beacon that should be resurrected to be verified. + m_expired_pending.clear(); + + // Mark remaining pending beacons that are expired with respect to pending age as expired and move to the expired map. for (auto iter = m_pending.begin(); iter != m_pending.end(); /* no-op */) { PendingBeacon pending_beacon(*iter->second); + // If the pending beacon has expired with no action remove the pending beacon. if (pending_beacon.PendingExpired(superblock_time)) { // Set the expired pending beacon's previous beacon hash to the beacon entry's hash. pending_beacon.m_previous_hash = pending_beacon.m_hash; @@ -948,7 +1112,19 @@ void BeaconRegistry::ActivatePending( pending_beacon.m_hash.GetHex()); // Insert the expired pending beacon into the db. - m_beacon_db.insert(pending_beacon.m_hash, height, static_cast(pending_beacon)); + if (!m_beacon_db.insert(pending_beacon.m_hash, height, static_cast(pending_beacon))) { + LogPrintf("WARN: %s: Attempt to insert an expired pending beacon entry for cpid %s in the beacon registry where " + "one with that hash key (%s) already exists.", + __func__, + pending_beacon.m_cpid.ToString(), + pending_beacon.m_hash.GetHex()); + } + + // Insert the expired pending beacon into the m_expired_pending set. We do the find here because the insert above + // created a shared pointer to the beacon object we want to hold a reference to. To save memory we do not want to + // use a copy. + m_expired_pending.insert(m_beacon_db.find(pending_beacon.m_hash)->second); + // Remove the pending beacon entry from the m_pending map. iter = m_pending.erase(iter); } else { @@ -965,12 +1141,13 @@ void BeaconRegistry::Deactivate(const uint256 superblock_hash) // Find beacons that were activated by the superblock to be reverted and restore them to pending status. These come // from the beacon db. for (auto iter = m_beacons.begin(); iter != m_beacons.end();) { - Cpid cpid = iter->second->m_cpid; - uint256 activation_hash = Hash(superblock_hash, iter->second->m_previous_hash); // If we have an active beacon whose hash matches the composite hash assigned by ActivatePending... if (iter->second->m_hash == activation_hash) { - // Find the pending beacon entry in the db before the activation. This is the previous state record. + Cpid cpid = iter->second->m_cpid; + + // Find the pending beacon entry in the db before the activation. This is the previous state record. NOTE that this + // find pulls the record from leveldb back into memory if the record had been passivated for memory savings before. auto pending_beacon_entry = m_beacon_db.find(iter->second->m_previous_hash); // If not found for some reason, move on. @@ -1000,64 +1177,59 @@ void BeaconRegistry::Deactivate(const uint256 superblock_hash) } } - // Find pending beacons that were removed from the pending beacon map and marked PENDING_EXPIRED and restore them - // back to pending status. Unfortunately, the beacon_db has to be traversed for this, because it is the only entity - // that has the records at this point. This will only be done very rarely, when a reorganization crosses a - // superblock commit. - auto iter = m_beacon_db.begin(); - - while (iter != m_beacon_db.end()) - { - // The cpid in the historical beacon record to be matched. - Cpid cpid = iter->second->m_cpid; - - uint256 match_hash = Hash(superblock_hash, iter->second->m_previous_hash); - - // If the calculated match_hash matches the key (hash) of the historical beacon record, then - // restore the previous record pointed to by the historical beacon record to the pending map. - if (match_hash == iter->first) - { - uint256 resurrect_pending_hash = iter->second->m_previous_hash; - - if (!resurrect_pending_hash.IsNull()) - { - Beacon_ptr resurrected_pending = m_beacon_db.find(resurrect_pending_hash)->second; - - // Check that the status of the beacon to resurrect is PENDING. If it is not log an error but continue - // anyway. - if (resurrected_pending->m_status != BeaconStatusForStorage::PENDING) - { - error("%s: Superblock hash %s: The beacon for cpid %s pointed to by an EXPIRED_PENDING beacon to be " - "put back in PENDING status does not have the expected status of PENDING. The beacon hash is %s " - "and the status is %i", - __func__, - superblock_hash.GetHex(), - cpid.ToString(), - resurrected_pending->m_hash.GetHex(), - resurrected_pending->m_status.Raw()); - } - - // Put the record in m_pending. - m_pending[resurrected_pending->GetId()] = resurrected_pending; - } - else - { - error("%s: Superblock hash %s: The beacon for cpid %s with an EXPIRED_PENDING status has no valid " - "previous beacon hash with which to restore the PENDING beacon.", + // With the newer m_expired_pending set, the resurrection of expired pending beacons is relatively painless. We traverse + // the m_expired_pending set and simply restore the pending beacon pointed to as the antecedent of each expired beacon in + // the map. This is done by the m_beacon_db.find which will pull the beacon record from leveldb if it does not exist in + // memory, which makes this passivation-proof up to a reorganization depth of the interval between two SB's (approximately + // 960 blocks). + for (const auto& iter : m_expired_pending) { + // Get the pending beacon entry that is the antecedent of the expired entry. + auto pending_beacon_entry = m_beacon_db.find(iter->m_previous_hash); + + // Resurrect pending beacon entry + if (!m_pending.insert(std::make_pair(pending_beacon_entry->second->GetId(), pending_beacon_entry->second)).second) { + LogPrintf("WARN: %s: Resurrected pending beacon entry, hash %s, from expired pending beacon for cpid %s during deactivation " + " of superblock hash %s already exists in the pending beacon map corresponding to beacon address %s.", __func__, + pending_beacon_entry->second->m_hash.GetHex(), + pending_beacon_entry->second->m_cpid.ToString(), superblock_hash.GetHex(), - cpid.ToString()); - } - } //matched EXPIRED_PENDING record + pending_beacon_entry->second->GetAddress().ToString() + ); + } + } - iter = m_beacon_db.advance(iter); - } // m_beacon_db traversal -} + // We clear the expired pending beacon map, as when the chain moves forward (perhaps on a different fork), the SB boundary will + // (during the activation) repopulate the m_expired_pending map with a new set of expired_beacons. (This is very, very likely + // to be the same set, BTW.) + m_expired_pending.clear(); + + // Note that making this foolproof in a reorganization across more than one SB boundary means we would have to repopulate the + // expired pending beacon map from the PREVIOUS set of expired pending beacons. This would require a traversal of the entire + // leveldb beacon structure for beacons, as it is keyed by beacon hash, not CPID or CKeyID. The expense is not worth it. In + // artificial reorgs for testing purposes on testnet, where the chain is reorganized back thousands of blocks and then reorganized + // forward along the same effective branch, the same superblocks will be restored using the same beacon activations as before, + // which means in effect none of the expired beacons are ever used. In a real fork scenario, not repopulating the expired_pending + // map limits the 100% foolproof reorg to the interval between SB's, which is approximately 960 blocks. This depth of reorg + // in an operational network scenario is almost inconceivable, and if it actually happens we have other problems much worse + // than the SLIGHT possibility of a different pending beacon being activated with the committed SB. + + // The original algorithm, which traversed m_beacon_db using an iterator, was actually broken, because passivation removes + // elements from the m_beacon_db in memory map if there is only one remaining reference, which is the m_historical map that holds + // references to all historical (non-current) entries. In the original algorithm, expired_pending entries were created in the + // m_beacon_db, and the pending beacon pointer references were removed from m_pending, but no in memory map other than + // m_historical kept a reference to the expired entry. This qualified the expired entry for passivation, so would + // not necessarily be present to find in an iterator traversal of m_beacon_db. The iterator style traversal of m_beacon_db, unlike + // the find, does NOT have the augmentation to pull passivated items from leveldb not in memory, because this would be + // exceedingly expensive. + } //! //! \brief BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries. This is a specialization of the RegistryDB template -//! HandleCurrentHistoricalEntries specific to Beacons. It handles the pending/active/renawal/expired pending/deleted -//! states and their interaction with the active entries map (m_beacons) and the pending entries map (m_pending). +//! HandleCurrentHistoricalEntries specific to Beacons. It handles the pending/active/renewal/expired pending/deleted +//! states and their interaction with the active entries map (m_beacons) and the pending entries map (m_pending) when loading +//! the beacon history from the beacon leveldb backing store during registry initialization. It is not intended to be used +//! for other specializations/overrides. //! //! \param entries //! \param pending_entries @@ -1068,6 +1240,7 @@ void BeaconRegistry::Deactivate(const uint256 superblock_hash) //! template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::BeaconRegistry::BeaconMap& entries, GRC::BeaconRegistry::PendingBeaconMap& pending_entries, + std::set& expired_entries, const Beacon& entry, entry_ptr& historical_entry_ptr, const uint64_t& recnum, @@ -1102,7 +1275,7 @@ template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::Be if (entry.m_status == BeaconStatusForStorage::ACTIVE || entry.m_status == BeaconStatusForStorage::RENEWAL) { - LogPrint(LogFlags::CONTRACT, "INFO: %s: %ss: entry insert: cpid %s, address %s, timestamp %" PRId64 ", " + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: entry insert: cpid %s, address %s, timestamp %" PRId64 ", " "hash %s, previous_hash %s, status %s, recnum %" PRId64 ".", __func__, key_type, @@ -1139,6 +1312,15 @@ template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::Be } } + if (entry.m_status == BeaconStatusForStorage::ACTIVE) { + // Note that in the original activation, all the activations happen for a superblock, and then the expired_entry set is + // cleared and then new expired entries recorded from the just committed SB. This method operates at the record level, but + // clearing the expired_entries for each ACTIVE record posting will achieve the same effect, because the entries are ordered + // the proper way. It is a little bit of undesired work, but it is not worth the complexity of feeding the boundaries + // of the group of verified beacons to activate. + expired_entries.clear(); + } + if (entry.m_status == BeaconStatusForStorage::EXPIRED_PENDING) { LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: expired pending entry delete: cpid %s, address %s, timestamp %" PRId64 ", " @@ -1154,6 +1336,9 @@ template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::Be recnum ); + // Insert the expired pending entry into the expired entries set. + expired_entries.insert(historical_entry_ptr); + // Delete any entry in the pending map that is marked expired. pending_entries.erase(entry.GetId()); } @@ -1180,7 +1365,7 @@ template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::Be int BeaconRegistry::Initialize() { - int height = m_beacon_db.Initialize(m_beacons, m_pending); + int height = m_beacon_db.Initialize(m_beacons, m_pending, m_expired_pending); LogPrint(LogFlags::BEACON, "INFO: %s: m_beacon_db size after load: %u", __func__, m_beacon_db.size()); LogPrint(LogFlags::BEACON, "INFO: %s: m_beacons size after load: %u", __func__, m_beacons.size()); @@ -1192,6 +1377,7 @@ void BeaconRegistry::ResetInMemoryOnly() { m_beacons.clear(); m_pending.clear(); + m_expired_pending.clear(); m_beacon_db.clear_in_memory_only(); } diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index df941b8ad4..64970d7bac 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -13,7 +13,6 @@ #include "gridcoin/contract/registry_db.h" #include "gridcoin/cpid.h" #include "gridcoin/support/enumbytes.h" - #include #include #include @@ -545,13 +544,10 @@ class BeaconRegistry : public IContractHandler //! Version 0: <= 5.2.0.0 //! Version 1: = 5.2.1.0 //! Version 2: 5.2.1.0 with hotfix and > 5.2.1.0 - //! - //! The current version of the beacon db is 2. No changes to the underlying storage have - //! occurred during the refactor to the registry db template, so this version remains unchanged - //! through 5.4.2.0+ + //! Version 3: 5.4.5.5+ //! BeaconRegistry() - : m_beacon_db(2) + : m_beacon_db(3) { }; @@ -589,6 +585,12 @@ class BeaconRegistry : public IContractHandler //! const PendingBeaconMap& PendingBeacons() const; + //! + //! \brief Get the set of beacons that have expired while pending (awaiting verification) + //! \return A reference to the expired pending beacon set. + //! + const std::set& ExpiredBeacons() const; + //! //! \brief Get the beacon for the specified CPID. //! @@ -768,6 +770,18 @@ class BeaconRegistry : public IContractHandler //! uint64_t PassivateDB(); + //! + //! \brief This function walks the linked beacon entries back (using the m_previous_hash member) from a provided + //! beacon to find the initial advertisement. Note that this does NOT traverse non-continuous beacon ownership, + //! which occurs when a beacon is allowed to expire and must be reverified under a new key. + //! + //! \param beacon smart shared pointer to beacon entry to begin walking back + //! \param beacon_chain_out shared pointer to UniValue beacon chain out report array + //! \return root (advertisement) beacon entry smart shared pointer + //! + Beacon_ptr GetBeaconChainletRoot(Beacon_ptr beacon, + std::shared_ptr>> beacon_chain_out = nullptr); + //! //! \brief Returns whether IsContract correction is needed in ReplayContracts during initialization //! \return @@ -794,6 +808,7 @@ class BeaconRegistry : public IContractHandler BeaconStatusForStorage, BeaconMap, PendingBeaconMap, + std::set, HistoricalBeaconMap> BeaconDB; private: @@ -805,6 +820,22 @@ class BeaconRegistry : public IContractHandler BeaconMap m_beacons; //!< Contains the active registered beacons. PendingBeaconMap m_pending; //!< Contains beacons awaiting verification. + //! + //! \brief Contains pending beacons that have expired. + //! + //! Contains pending beacons that have expired but need to be retained until the next SB (activation) to ensure a + //! reorganization will successfully resurrect expired pending beacons back into pending ones up to the depth equal to one SB to + //! the next, which is about 960 blocks. The reason this is necessary is two fold: 1) it makes the lookup for expired + //! pending beacons in the deactivate method much simpler in the case of a reorg across a SB boundary, and 2) it holds + //! a reference to the pending beacon shared pointer object in the history map, which prevents it from being passivated. + //! Otherwise, a passivation event, which would remove the pending deleted beacons, followed by a reorganization across + //! SB boundary could have a small possibility of removing a pending beacon that could be verified in the alternative SB + //! eventually staked. + //! + //! This set is cleared and repopulated at each SB accepted by the node with the current expired pending beacons. + //! + std::set m_expired_pending; + //! //! \brief The member variable that is the instance of the beacon database. This is private to the //! beacon registry and is only accessible by beacon registry functions. diff --git a/src/gridcoin/contract/registry_db.h b/src/gridcoin/contract/registry_db.h index 826aa69bdf..41f37e2627 100644 --- a/src/gridcoin/contract/registry_db.h +++ b/src/gridcoin/contract/registry_db.h @@ -26,9 +26,11 @@ namespace GRC { //! M: the map type for the entries //! P: the map type for pending entries. This is really only used for beacons. In all other registries it is typedef'd to //! the same as M. +//! X: the map type for expired pending entries. This is really only used for beacons. In all other registries it is typedef'd to +//! the same as M. //! H: the historical map type for historical entries //! -template +template class RegistryDB { public: @@ -62,10 +64,12 @@ class RegistryDB //! \param entries The map of current entries. //! \param pending_entries. The map of pending entries. This is not used in the general template, only in the beacons //! specialization. + //! \param expired_entries. The map of expired pending entries. This is not used in the general template, only in the + //! beacons specialization. //! //! \return block height up to and including which the entry records were stored. //! - int Initialize(M& entries, P& pending_entries) + int Initialize(M& entries, P& pending_entries, X& expired_entries) { bool status = true; int height = 0; @@ -169,7 +173,7 @@ class RegistryDB m_historical[iter.second.m_hash] = std::make_shared(entry); entry_ptr& historical_entry_ptr = m_historical[iter.second.m_hash]; - HandleCurrentHistoricalEntries(entries, pending_entries, entry, + HandleCurrentHistoricalEntries(entries, pending_entries, expired_entries, entry, historical_entry_ptr, recnum, key_type); number_passivated += (uint64_t) HandlePreviousHistoricalEntries(historical_entry_ptr); @@ -199,7 +203,7 @@ class RegistryDB //! \param recnum //! \param key_type //! - void HandleCurrentHistoricalEntries(M& entries, P& pending_entries, const E& entry, + void HandleCurrentHistoricalEntries(M& entries, P& pending_entries, X& expired_entries, const E& entry, entry_ptr& historical_entry_ptr, const uint64_t& recnum, const std::string& key_type) { @@ -226,7 +230,7 @@ class RegistryDB } //! - //! \brief Handles the passivation of previous historical entries that have been superceded by current entries. + //! \brief Handles the passivation of previous historical entries that have been superseded by current entries. //! //! \param historical_entry_ptr. Shared smart pointer to current historical entry already inserted into historical map. //! @@ -326,8 +330,8 @@ class RegistryDB LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Passivated %" PRId64 " elements from %s entry db.", __func__, - KeyType(), - number_passivated); + number_passivated, + KeyType()); // Set needs passivation flag to false after passivating the db. m_needs_passivation = false; diff --git a/src/gridcoin/gridcoin.cpp b/src/gridcoin/gridcoin.cpp index 5ef78ce340..39853e113f 100644 --- a/src/gridcoin/gridcoin.cpp +++ b/src/gridcoin/gridcoin.cpp @@ -214,8 +214,8 @@ void InitializeContracts(CBlockIndex* pindexBest) // for polls and votes. The reason for this is quite simple. Polls and votes are UNIQUE. The reversion of an add // is simply to delete them. The wallet startup replay requirement is still required for polls and votes, because // the Poll/Vote classes do not have a backing registry db yet. - const int& start_height = std::min(std::max(db_heights.GetLowestRegistryBlockHeight(), V11_height), - lookback_window_low_height); + int start_height = std::min(std::max(db_heights.GetLowestRegistryBlockHeight(), V11_height), + lookback_window_low_height); LogPrintf("Gridcoin: Starting contract replay from height %i.", start_height); diff --git a/src/gridcoin/md5.c b/src/gridcoin/md5.c index 18a0923825..65ccee979f 100644 --- a/src/gridcoin/md5.c +++ b/src/gridcoin/md5.c @@ -6,7 +6,7 @@ * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions + * the following conditions are adhered to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms @@ -31,7 +31,7 @@ * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library + * The word 'cryptographic' can be left out if the routines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: @@ -49,7 +49,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * The licence and distribution terms for any publically available version or + * The licence and distribution terms for any publicly available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ @@ -244,7 +244,7 @@ uint8_t *GRC__MD5(const uint8_t *data, size_t len, uint8_t out[MD5_DIGEST_LENGTH // As pointed out by Wei Dai , the above can be // simplified to the code below. Wei attributes these optimizations -// to Peter Gutmann's SHS code, and he attributes it to Rich Schroeppel. +// to Peter Gutmann's SSH code, and he attributes it to Rich Schroeppel. #define F(b, c, d) ((((c) ^ (d)) & (b)) ^ (d)) #define G(b, c, d) ((((b) ^ (c)) & (d)) ^ (c)) #define H(b, c, d) ((b) ^ (c) ^ (d)) diff --git a/src/gridcoin/mrc.cpp b/src/gridcoin/mrc.cpp index e3db165ad7..ef8fcc7205 100644 --- a/src/gridcoin/mrc.cpp +++ b/src/gridcoin/mrc.cpp @@ -280,8 +280,6 @@ bool TrySignMRC( CBlockIndex* pindex, GRC::MRC& mrc) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - AssertLockHeld(cs_main); - // lock needs to be taken on pwallet here. LOCK(pwallet->cs_wallet); diff --git a/src/gridcoin/project.cpp b/src/gridcoin/project.cpp index 3c92fd8792..6db6511067 100644 --- a/src/gridcoin/project.cpp +++ b/src/gridcoin/project.cpp @@ -495,7 +495,7 @@ int Whitelist::Initialize() { LOCK(cs_lock); - int height = m_project_db.Initialize(m_project_entries, m_pending_project_entries); + int height = m_project_db.Initialize(m_project_entries, m_pending_project_entries, m_expired_project_entries); LogPrint(LogFlags::CONTRACT, "INFO: %s: m_project_db size after load: %u", __func__, m_project_db.size()); LogPrint(LogFlags::CONTRACT, "INFO: %s: m_project_entries size after load: %u", __func__, m_project_entries.size()); diff --git a/src/gridcoin/project.h b/src/gridcoin/project.h index c397e71bb9..5b6ff5fae2 100644 --- a/src/gridcoin/project.h +++ b/src/gridcoin/project.h @@ -618,13 +618,15 @@ class Whitelist : public IContractHandler static void RunDBPassivation(); //! - //! \brief Specializes the template RegistryDB for the ScraperEntry class + //! \brief Specializes the template RegistryDB for the ScraperEntry class. Note that std::set is not + //! actually used. //! typedef RegistryDB, HistoricalProjectEntryMap> ProjectEntryDB; private: @@ -644,6 +646,8 @@ class Whitelist : public IContractHandler ProjectEntryMap m_project_entries; //!< The set of whitelisted projects. PendingProjectEntryMap m_pending_project_entries {}; //!< Not actually used. Only to satisfy the template. + std::set m_expired_project_entries {}; //!< Not actually used. Only to satisfy the template. + ProjectEntryDB m_project_db; //!< The project db member public: diff --git a/src/gridcoin/protocol.cpp b/src/gridcoin/protocol.cpp index 4281f01a15..ec5f0831e9 100644 --- a/src/gridcoin/protocol.cpp +++ b/src/gridcoin/protocol.cpp @@ -482,7 +482,7 @@ int ProtocolRegistry::Initialize() { LOCK(cs_lock); - int height = m_protocol_db.Initialize(m_protocol_entries, m_pending_protocol_entries); + int height = m_protocol_db.Initialize(m_protocol_entries, m_pending_protocol_entries, m_expired_protocol_entries); LogPrint(LogFlags::CONTRACT, "INFO: %s: m_protocol_db size after load: %u", __func__, m_protocol_db.size()); LogPrint(LogFlags::CONTRACT, "INFO: %s: m_protocol_entries size after load: %u", __func__, m_protocol_entries.size()); diff --git a/src/gridcoin/protocol.h b/src/gridcoin/protocol.h index 3e409634e3..37aa39bc53 100644 --- a/src/gridcoin/protocol.h +++ b/src/gridcoin/protocol.h @@ -567,13 +567,15 @@ class ProtocolRegistry : public IContractHandler static void RunDBPassivation(); //! - //! \brief Specializes the template RegistryDB for the ProtocolEntry class + //! \brief Specializes the template RegistryDB for the ProtocolEntry class. Note that std::set + //! is not actually used. //! typedef RegistryDB, HistoricalProtocolEntryMap> ProtocolEntryDB; private: @@ -593,6 +595,8 @@ class ProtocolRegistry : public IContractHandler ProtocolEntryMap m_protocol_entries; //!< Contains the current protocol entries including entries marked DELETED. PendingProtocolEntryMap m_pending_protocol_entries {}; //!< Not used. Only to satisfy the template. + std::set m_expired_protocol_entries {}; //!< Not used. Only to satisfy the template. + ProtocolEntryDB m_protocol_db; public: diff --git a/src/gridcoin/quorum.cpp b/src/gridcoin/quorum.cpp index 92cd83f0f5..c0d04a4753 100644 --- a/src/gridcoin/quorum.cpp +++ b/src/gridcoin/quorum.cpp @@ -721,7 +721,7 @@ class SuperblockValidator std::vector m_resolved_parts; //! - //! \brief Divisor set by the \c ProjectCombiner for iteraton over each + //! \brief Divisor set by the \c ProjectCombiner for iteration over each //! convergence combination. //! size_t m_combiner_mask; diff --git a/src/gridcoin/scraper/http.cpp b/src/gridcoin/scraper/http.cpp index 149fc5a511..022aafe176 100644 --- a/src/gridcoin/scraper/http.cpp +++ b/src/gridcoin/scraper/http.cpp @@ -98,10 +98,18 @@ namespace { pg->lastruntime = currenttime; +#if LIBCURL_VERSION_NUM >= 0x073700 + curl_off_t speed; +#else double speed; +#endif CURLcode result; +#if LIBCURL_VERSION_NUM >= 0x073700 + result = curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD_T, &speed); +#else result = curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD, &speed); +#endif // Download speed update if (result == CURLE_OK) diff --git a/src/gridcoin/scraper/scraper.cpp b/src/gridcoin/scraper/scraper.cpp index 95788b33e6..9a65484b78 100755 --- a/src/gridcoin/scraper/scraper.cpp +++ b/src/gridcoin/scraper/scraper.cpp @@ -208,7 +208,7 @@ std::map> CScraperManifest::mapManife ConvergedScraperStats ConvergedScraperStatsCache GUARDED_BY(cs_ConvergedScraperStatsCache) = {}; /** - * @brief Scraper loggger function + * @brief Scraper logger function * @param eType * @param sCall * @param sMessage diff --git a/src/gridcoin/scraper/scraper_registry.cpp b/src/gridcoin/scraper/scraper_registry.cpp index 147253f39a..bf481e0216 100644 --- a/src/gridcoin/scraper/scraper_registry.cpp +++ b/src/gridcoin/scraper/scraper_registry.cpp @@ -527,7 +527,7 @@ int ScraperRegistry::Initialize() { LOCK(cs_lock); - int height = m_scraper_db.Initialize(m_scrapers, m_pending_scrapers); + int height = m_scraper_db.Initialize(m_scrapers, m_pending_scrapers, m_expired_scraper_entries); LogPrint(LogFlags::SCRAPER, "INFO: %s: m_scraper_db size after load: %u", __func__, m_scraper_db.size()); LogPrint(LogFlags::SCRAPER, "INFO: %s: m_scrapers size after load: %u", __func__, m_scrapers.size()); diff --git a/src/gridcoin/scraper/scraper_registry.h b/src/gridcoin/scraper/scraper_registry.h index 5705c15d57..5686c25991 100644 --- a/src/gridcoin/scraper/scraper_registry.h +++ b/src/gridcoin/scraper/scraper_registry.h @@ -606,13 +606,15 @@ class ScraperRegistry : public IContractHandler static void RunDBPassivation(); //! - //! \brief Specializes the template RegistryDB for the ScraperEntry class + //! \brief Specializes the template RegistryDB for the ScraperEntry class. Note that std::set is + //! not actually used. //! typedef RegistryDB, HistoricalScraperMap> ScraperEntryDB; private: @@ -632,6 +634,8 @@ class ScraperRegistry : public IContractHandler ScraperMap m_scrapers; //!< Contains the current scraper entries, including entries marked DELETED. PendingScraperMap m_pending_scrapers {}; //!< Not actually used for scrapers. To satisfy the template only. + std::set m_expired_scraper_entries {}; //!< Not actually used for scrapers. To satisfy the template only. + ScraperEntryDB m_scraper_db; public: diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index 938d906c47..f96bd840db 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -48,6 +48,14 @@ Allocation::Allocation(const Fraction& f) : Fraction(f) {} +Allocation::Allocation(const int64_t& numerator, const int64_t& denominator) + : Fraction(numerator, denominator) +{} + +Allocation::Allocation(const int64_t& numerator, const int64_t& denominator, const bool& simplify) + : Fraction(numerator, denominator, simplify) +{} + CAmount Allocation::ToCAmount() const { return GetNumerator() / GetDenominator(); @@ -58,6 +66,146 @@ double Allocation::ToPercent() const return ToDouble() * 100.0; } +Allocation Allocation::operator+(const Allocation& rhs) const +{ + return static_cast(Fraction::operator+(rhs)); +} + +Allocation Allocation::operator+(const int64_t& rhs) const +{ + return static_cast(Fraction::operator+(rhs)); +} + +Allocation Allocation::operator-(const Allocation& rhs) const +{ + return static_cast(Fraction::operator-(rhs)); +} + +Allocation Allocation::operator-(const int64_t& rhs) const +{ + return static_cast(Fraction::operator-(rhs)); +} + +Allocation Allocation::operator*(const Allocation& rhs) const +{ + return static_cast(Fraction::operator*(rhs)); +} + +Allocation Allocation::operator*(const int64_t& rhs) const +{ + return static_cast(Fraction::operator*(rhs)); +} + +Allocation Allocation::operator/(const Allocation& rhs) const +{ + return static_cast(Fraction::operator/(rhs)); +} + +Allocation Allocation::operator/(const int64_t& rhs) const +{ + return static_cast(Fraction::operator/(rhs)); +} + +Allocation Allocation::operator+=(const Allocation& rhs) +{ + return static_cast(Fraction::operator+=(rhs)); +} + +Allocation Allocation::operator+=(const int64_t& rhs) +{ + return static_cast(Fraction::operator+=(rhs)); +} + +Allocation Allocation::operator-=(const Allocation& rhs) +{ + return static_cast(Fraction::operator-=(rhs)); +} + +Allocation Allocation::operator-=(const int64_t& rhs) +{ + return static_cast(Fraction::operator-=(rhs)); +} + +Allocation Allocation::operator*=(const Allocation& rhs) +{ + return static_cast(Fraction::operator*=(rhs)); +} + +Allocation Allocation::operator*=(const int64_t& rhs) +{ + return static_cast(Fraction::operator*=(rhs)); +} + +Allocation Allocation::operator/=(const Allocation& rhs) +{ + return static_cast(Fraction::operator/=(rhs)); +} + +Allocation Allocation::operator/=(const int64_t& rhs) +{ + return static_cast(Fraction::operator/=(rhs)); +} + +bool Allocation::operator==(const Allocation& rhs) const +{ + return Fraction::operator==(rhs); +} + +bool Allocation::operator!=(const Allocation& rhs) const +{ + return Fraction::operator!=(rhs); +} + +bool Allocation::operator<=(const Allocation& rhs) const +{ + return Fraction::operator<=(rhs); +} + +bool Allocation::operator>=(const Allocation& rhs) const +{ + return Fraction::operator>=(rhs); +} + +bool Allocation::operator<(const Allocation& rhs) const +{ + return Fraction::operator<(rhs); +} + +bool Allocation::operator>(const Allocation& rhs) const +{ + return Fraction::operator>(rhs); +} + +bool Allocation::operator==(const int64_t& rhs) const +{ + return Fraction::operator==(rhs); +} + +bool Allocation::operator!=(const int64_t& rhs) const +{ + return Fraction::operator!=(rhs); +} + +bool Allocation::operator<=(const int64_t& rhs) const +{ + return Fraction::operator<=(rhs); +} + +bool Allocation::operator>=(const int64_t& rhs) const +{ + return Fraction::operator>=(rhs); +} + +bool Allocation::operator<(const int64_t& rhs) const +{ + return Fraction::operator<(rhs); +} + +bool Allocation::operator>(const int64_t& rhs) const +{ + return Fraction::operator>(rhs); +} + // ----------------------------------------------------------------------------- // Class: LocalSideStake // ----------------------------------------------------------------------------- @@ -712,7 +860,7 @@ int SideStakeRegistry::Initialize() { LOCK(cs_lock); - int height = m_sidestake_db.Initialize(m_mandatory_sidestake_entries, m_pending_sidestake_entries); + int height = m_sidestake_db.Initialize(m_mandatory_sidestake_entries, m_pending_sidestake_entries, m_expired_sidestake_entries); SubscribeToCoreSignals(); diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index 8702701643..42613e938c 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -43,9 +43,26 @@ class Allocation : public Fraction //! Allocation(const Fraction& f); + //! + //! \brief Initialize an allocation directly from specifying a numerator and denominator + //! \param numerator + //! \param denominator + //! + Allocation(const int64_t& numerator, const int64_t& denominator); + + //! + //! \brief Initialize an allocation directly from specifying a numerator and denominator, specifying the simplification + //! directive. + //! + //! \param numerator + //! \param denominator + //! \param simplify + //! + Allocation(const int64_t& numerator, const int64_t& denominator, const bool& simplify); + //! //! \brief Allocations extend the Fraction class and can also represent the result of the allocation constructed fraction - //! and the result of the muliplication of that fraction times the reward, which is in CAmount (i.e. int64_t). + //! and the result of the multiplication of that fraction times the reward, which is in CAmount (i.e. int64_t). //! //! \return CAmount of the Fraction representation of the actual allocation. //! @@ -57,6 +74,35 @@ class Allocation : public Fraction //! \return double percent representation of the allocation fraction. //! double ToPercent() const; + + Allocation operator+(const Allocation& rhs) const; + Allocation operator+(const int64_t& rhs) const; + Allocation operator-(const Allocation& rhs) const; + Allocation operator-(const int64_t& rhs) const; + Allocation operator*(const Allocation& rhs) const; + Allocation operator*(const int64_t& rhs) const; + Allocation operator/(const Allocation& rhs) const; + Allocation operator/(const int64_t& rhs) const; + Allocation operator+=(const Allocation& rhs); + Allocation operator+=(const int64_t& rhs); + Allocation operator-=(const Allocation& rhs); + Allocation operator-=(const int64_t& rhs); + Allocation operator*=(const Allocation& rhs); + Allocation operator*=(const int64_t& rhs); + Allocation operator/=(const Allocation& rhs); + Allocation operator/=(const int64_t& rhs); + bool operator==(const Allocation& rhs) const; + bool operator!=(const Allocation& rhs) const; + bool operator<=(const Allocation& rhs) const; + bool operator>=(const Allocation& rhs) const; + bool operator<(const Allocation& rhs) const; + bool operator>(const Allocation& rhs) const; + bool operator==(const int64_t& rhs) const; + bool operator!=(const int64_t& rhs) const; + bool operator<=(const int64_t& rhs) const; + bool operator>=(const int64_t& rhs) const; + bool operator<(const int64_t& rhs) const; + bool operator>(const int64_t& rhs) const; }; //! @@ -783,13 +829,15 @@ class SideStakeRegistry : public IContractHandler static void RunDBPassivation(); //! - //! \brief Specializes the template RegistryDB for the SideStake class + //! \brief Specializes the template RegistryDB for the SideStake class. Note that std::set + //! is not actually used. //! typedef RegistryDB, HistoricalSideStakeMap> SideStakeDB; private: @@ -827,6 +875,8 @@ class SideStakeRegistry : public IContractHandler MandatorySideStakeMap m_mandatory_sidestake_entries; //!< Contains the mandatory sidestake entries, including DELETED. PendingSideStakeMap m_pending_sidestake_entries {}; //!< Not used. Only to satisfy the template. + std::set m_expired_sidestake_entries {}; //!< Not used. Only to satisfy the template. + SideStakeDB m_sidestake_db; //!< The internal sidestake db object for leveldb persistence. bool m_local_entry_already_saved_to_config = false; //!< Flag to prevent reload on signal if individual entry saved already. diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 2c54e215f4..0f92700364 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -1228,14 +1228,15 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe return accrual; } - Beacon_ptr beacon_ptr = beacon; + Beacon_ptr beacon_ptr; // Walk back the entries in the historical beacon map linked by renewal prev tx hash until the first // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier // than here. - while (beacon_ptr->Renewed()) - { - beacon_ptr = beacons.GetBeaconDB().find(beacon_ptr->m_previous_hash)->second; + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon); + } catch (std::runtime_error& e) { + std::abort(); } const CBlockIndex* pindex_baseline = GRC::Tally::GetBaseline(); diff --git a/src/gridcoin/upgrade.cpp b/src/gridcoin/upgrade.cpp index aa45fe12bd..f52d08e1bf 100644 --- a/src/gridcoin/upgrade.cpp +++ b/src/gridcoin/upgrade.cpp @@ -32,12 +32,16 @@ Upgrade::Upgrade() void Upgrade::ScheduledUpdateCheck() { - std::string VersionResponse = ""; + std::string VersionResponse; + std::string change_log; + + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - CheckForLatestUpdate(VersionResponse); + CheckForLatestUpdate(VersionResponse, change_log, upgrade_type); } -bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dialog, bool snapshotrequest) +bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, std::string& change_log, Upgrade::UpgradeType& upgrade_type, + bool ui_dialog, bool snapshotrequest) { // If testnet skip this || If the user changes this to disable while wallet running just drop out of here now. // (Need a way to remove items from scheduler.) @@ -46,8 +50,8 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial Http VersionPull; - std::string GithubResponse = ""; - std::string VersionResponse = ""; + std::string GithubResponse; + std::string VersionResponse; // We receive the response and it's in a json reply UniValue Response(UniValue::VOBJ); @@ -64,15 +68,15 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial if (VersionResponse.empty()) { - LogPrintf("WARNING %s: No Response from GitHub", __func__); + LogPrintf("WARNING: %s: No Response from GitHub", __func__); return false; } - std::string GithubReleaseData = ""; - std::string GithubReleaseTypeData = ""; - std::string GithubReleaseBody = ""; - std::string GithubReleaseType = ""; + std::string GithubReleaseData; + std::string GithubReleaseTypeData; + std::string GithubReleaseBody; + std::string GithubReleaseType; try { @@ -95,14 +99,19 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial } GithubReleaseTypeData = ToLower(GithubReleaseTypeData); - if (GithubReleaseTypeData.find("leisure") != std::string::npos) - GithubReleaseType = _("leisure"); - else if (GithubReleaseTypeData.find("mandatory") != std::string::npos) + if (GithubReleaseTypeData.find("leisure") != std::string::npos) { + GithubReleaseType = _("leisure"); + upgrade_type = Upgrade::UpgradeType::Leisure; + } else if (GithubReleaseTypeData.find("mandatory") != std::string::npos) { GithubReleaseType = _("mandatory"); - - else + // This will be confirmed below by also checking the second position version. If not incremented, then it will + // be set to unknown. + upgrade_type = Upgrade::UpgradeType::Mandatory; + } else { GithubReleaseType = _("unknown"); + upgrade_type = Upgrade::UpgradeType::Unknown; + } // Parse version data std::vector GithubVersion; @@ -113,6 +122,7 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial LocalVersion.push_back(CLIENT_VERSION_MAJOR); LocalVersion.push_back(CLIENT_VERSION_MINOR); LocalVersion.push_back(CLIENT_VERSION_REVISION); + LocalVersion.push_back(CLIENT_VERSION_BUILD); if (GithubVersion.size() != 4) { @@ -123,60 +133,79 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial bool NewVersion = false; bool NewMandatory = false; + bool same_version = true; try { // Left to right version numbers. - // 3 numbers to check for production. - for (unsigned int x = 0; x < 3; x++) - { + // 4 numbers to check. + for (unsigned int x = 0; x <= 3; x++) { int github_version = 0; - if (!ParseInt32(GithubVersion[x], &github_version)) - { + if (!ParseInt32(GithubVersion[x], &github_version)) { throw std::invalid_argument("Failed to parse GitHub version from official GitHub project repo."); } - if (github_version > LocalVersion[x]) - { + if (github_version > LocalVersion[x]) { NewVersion = true; - if (x < 2) - { + same_version = false; + + if (x < 2 && upgrade_type == Upgrade::UpgradeType::Mandatory) { NewMandatory = true; + } else { + upgrade_type = Upgrade::UpgradeType::Unknown; } - break; + } else { + same_version &= (github_version == LocalVersion[x]); } } - } - catch (std::exception& ex) - { + } catch (std::exception& ex) { error("%s: Exception occurred checking client version against GitHub version (%s)", __func__, ToString(ex.what())); + upgrade_type = Upgrade::UpgradeType::Unknown; return false; } - if (!NewVersion) return NewVersion; - - // New version was found + // Populate client_message_out regardless of whether new version is found, because we are using this method for + // the version information button in the "About Gridcoin" dialog. client_message_out = _("Local version: ") + strprintf("%d.%d.%d.%d", CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD) + "\r\n"; client_message_out.append(_("GitHub version: ") + GithubReleaseData + "\r\n"); - client_message_out.append(_("This update is ") + GithubReleaseType + "\r\n\r\n"); - // For snapshot requests we will handle things differently after this point - if (snapshotrequest && NewMandatory) - return NewVersion; + if (NewVersion) { + client_message_out.append(_("This update is ") + GithubReleaseType + "\r\n\r\n"); + } else if (same_version) { + client_message_out.append(_("The latest release is ") + GithubReleaseType + "\r\n\r\n"); + client_message_out.append(_("You are running the latest release.") + "\n"); + } else { + client_message_out.append(_("The latest release is ") + GithubReleaseType + "\r\n\r\n"); + + // If not a new version available and the version is not the same, the only thing left is that we are running + // a version greater than the latest release version, so set the upgrade_type to Unsupported, which is used for a + // warning. + upgrade_type = Upgrade::UpgradeType::Unsupported; + client_message_out.append(_("WARNING: You are running a version that is higher than the latest release.") + "\n"); + } + + change_log = GithubReleaseBody; + + if (!NewVersion) return false; - if (NewMandatory) + // For snapshot requests we will only return true if there is a new mandatory version AND the snapshotrequest boolean + // is set true. This is because the snapshot request context is looking for the presence of a new mandatory to block + // the snapshot download before upgrading to the new mandatory if there is one. + if (snapshotrequest && NewMandatory) return true; + + if (NewMandatory) { client_message_out.append(_("WARNING: A mandatory release is available. Please upgrade as soon as possible.") + "\n"); + } - std::string ChangeLog = GithubReleaseBody; - - if (ui_dialog) - uiInterface.UpdateMessageBox(client_message_out, ChangeLog); + if (ui_dialog) { + uiInterface.UpdateMessageBox(client_message_out, static_cast(upgrade_type), change_log); + } - return NewVersion; + return true; } void Upgrade::SnapshotMain() @@ -188,8 +217,10 @@ void Upgrade::SnapshotMain() // Verify a mandatory release is not available before we continue to snapshot download. std::string VersionResponse = ""; + std::string change_log; + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - if (CheckForLatestUpdate(VersionResponse, false, true)) + if (CheckForLatestUpdate(VersionResponse, change_log, upgrade_type, false, true)) { std::cout << this->ResetBlockchainMessages(UpdateAvailable) << std::endl; std::cout << this->ResetBlockchainMessages(GithubResponse) << std::endl; diff --git a/src/gridcoin/upgrade.h b/src/gridcoin/upgrade.h index daa1efba8f..c4dac3fce9 100644 --- a/src/gridcoin/upgrade.h +++ b/src/gridcoin/upgrade.h @@ -125,6 +125,13 @@ class Upgrade GithubResponse }; + enum UpgradeType { + Unknown, + Leisure, + Mandatory, + Unsupported //! This is used for a running version that is greater than the current official release. + }; + //! //! \brief Scheduler call to CheckForLatestUpdate //! @@ -133,7 +140,8 @@ class Upgrade //! //! \brief Check for latest updates on GitHub. //! - static bool CheckForLatestUpdate(std::string& client_message_out, bool ui_dialog = true, bool snapshotrequest = false); + static bool CheckForLatestUpdate(std::string& client_message_out, std::string& change_log, UpgradeType& upgrade_type, + bool ui_dialog = true, bool snapshotrequest = false); //! //! \brief Function that will be threaded to download snapshot diff --git a/src/gridcoin/voting/builders.cpp b/src/gridcoin/voting/builders.cpp index 0bd2597c70..529a883028 100644 --- a/src/gridcoin/voting/builders.cpp +++ b/src/gridcoin/voting/builders.cpp @@ -1020,7 +1020,7 @@ PollBuilder PollBuilder::SetTitle(std::string title) ToString(Poll::MAX_TITLE_SIZE))); } - m_poll->m_title = std::move(title); + m_poll->m_title = title; return std::move(*this); } @@ -1037,7 +1037,7 @@ PollBuilder PollBuilder::SetUrl(std::string url) ToString(Poll::MAX_URL_SIZE))); } - m_poll->m_url = std::move(url); + m_poll->m_url = url; return std::move(*this); } @@ -1050,7 +1050,7 @@ PollBuilder PollBuilder::SetQuestion(std::string question) ToString(Poll::MAX_QUESTION_SIZE))); } - m_poll->m_question = std::move(question); + m_poll->m_question = question; return std::move(*this); } @@ -1059,13 +1059,13 @@ PollBuilder PollBuilder::SetChoices(std::vector labels) { m_poll->m_choices = Poll::ChoiceList(); - return AddChoices(std::move(labels)); + return AddChoices(labels); } PollBuilder PollBuilder::AddChoices(std::vector labels) { for (auto& label : labels) { - *this = AddChoice(std::move(label)); + *this = AddChoice(label); } return std::move(*this); @@ -1096,7 +1096,7 @@ PollBuilder PollBuilder::AddChoice(std::string label) throw VotingError(strprintf(_("Duplicate poll choice: %s"), label)); } - m_poll->m_choices.Add(std::move(label)); + m_poll->m_choices.Add(label); return std::move(*this); } @@ -1105,20 +1105,20 @@ PollBuilder PollBuilder::SetAdditionalFields(std::vector { m_poll->m_additional_fields = Poll::AdditionalFieldList(); - return AddAdditionalFields(std::move(fields)); + return AddAdditionalFields(fields); } PollBuilder PollBuilder::SetAdditionalFields(Poll::AdditionalFieldList fields) { m_poll->m_additional_fields = Poll::AdditionalFieldList(); - return AddAdditionalFields(std::move(fields)); + return AddAdditionalFields(fields); } PollBuilder PollBuilder::AddAdditionalFields(std::vector fields) { for (auto& field : fields) { - *this = AddAdditionalField(std::move(field)); + *this = AddAdditionalField(field); } if (!m_poll->m_additional_fields.WellFormed(m_poll->m_type.Value())) { @@ -1131,7 +1131,7 @@ PollBuilder PollBuilder::AddAdditionalFields(std::vector PollBuilder PollBuilder::AddAdditionalFields(Poll::AdditionalFieldList fields) { for (auto& field : fields) { - *this = AddAdditionalField(std::move(field)); + *this = AddAdditionalField(field); } if (!m_poll->m_additional_fields.WellFormed(m_poll->m_type.Value())) { @@ -1158,26 +1158,26 @@ PollBuilder PollBuilder::AddAdditionalField(Poll::AdditionalField field) ToString(POLL_MAX_ADDITIONAL_FIELDS_SIZE))); } - if (field.m_name.size() > Poll::AdditionalField::MAX_N_OR_V_SIZE) { + if (field.m_name.size() > Poll::AdditionalField::MAX_NAME_SIZE) { throw VotingError(strprintf( _("Poll additional field name \"%s\" exceeds %s characters."), field.m_name, - ToString(Poll::AdditionalField::MAX_N_OR_V_SIZE))); + ToString(Poll::AdditionalField::MAX_NAME_SIZE))); } - if (field.m_value.size() > Poll::AdditionalField::MAX_N_OR_V_SIZE) { + if (field.m_value.size() > Poll::AdditionalField::MAX_VALUE_SIZE) { throw VotingError(strprintf( _("Poll additional field value \"%s\" for field name \"%s\" exceeds %s characters."), field.m_value, field.m_name, - ToString(Poll::AdditionalField::MAX_N_OR_V_SIZE))); + ToString(Poll::AdditionalField::MAX_VALUE_SIZE))); } if (m_poll->m_additional_fields.FieldExists(field.m_name)) { throw VotingError(strprintf(_("Duplicate poll additional field: %s"), field.m_name)); } - m_poll->m_additional_fields.Add(std::move(field)); + m_poll->m_additional_fields.Add(field); return std::move(*this); } diff --git a/src/gridcoin/voting/poll.h b/src/gridcoin/voting/poll.h index d05cea18a6..1decaae541 100644 --- a/src/gridcoin/voting/poll.h +++ b/src/gridcoin/voting/poll.h @@ -203,9 +203,15 @@ class Poll { public: //! - //! \brief The maximum length for a poll additional field name or value. + //! \brief The maximum length for a poll additional field name. //! - static constexpr size_t MAX_N_OR_V_SIZE = 100; + static constexpr size_t MAX_NAME_SIZE = 100; + + //! + //! \brief The maximum length for a poll additional field value. This is currently set to align with the + //! maximum Project URL length. + //! + static constexpr size_t MAX_VALUE_SIZE = 500; std::string m_name; std::string m_value; @@ -244,8 +250,8 @@ class Poll template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(LIMITED_STRING(m_name, MAX_N_OR_V_SIZE)); - READWRITE(LIMITED_STRING(m_value, MAX_N_OR_V_SIZE)); + READWRITE(LIMITED_STRING(m_name, MAX_NAME_SIZE)); + READWRITE(LIMITED_STRING(m_value, MAX_VALUE_SIZE)); READWRITE(m_required); } }; // AdditionalField diff --git a/src/gridcoin/voting/result.cpp b/src/gridcoin/voting/result.cpp index 95f0be85fe..962cf21303 100644 --- a/src/gridcoin/voting/result.cpp +++ b/src/gridcoin/voting/result.cpp @@ -109,6 +109,11 @@ class VoteCandidate { LOCK(pwalletMain->cs_wallet); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: vote contract tx hash %s ismine status %i", + __func__, + m_tx.GetHash().GetHex(), + pwalletMain->IsMine(m_tx)); + return pwalletMain->IsMine(m_tx); } @@ -1256,6 +1261,7 @@ void PollResult::TallyVote(VoteDetail detail) if (detail.m_ismine != ISMINE_NO) { bool choice_found = false; + // If the response offset entry already exists, then append to the existing entry... for (auto& choice : m_self_vote_detail.m_responses) { if (choice.first == response_offset) { choice.second += response_weight; @@ -1264,6 +1270,7 @@ void PollResult::TallyVote(VoteDetail detail) } } + // Otherwise make a new m_responses entry to represent the response offset and the associated weight. if (!choice_found) { m_self_vote_detail.m_responses.push_back(std::make_pair(response_offset, response_weight)); } diff --git a/src/init.cpp b/src/init.cpp index 286d5d11eb..5cb0bff00c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1144,15 +1144,22 @@ bool AppInit2(ThreadHandlerPtr threads) } // -tor can override normal proxy, -notor disables Tor entirely - if (gArgs.IsArgSet("-tor") && (fProxy || gArgs.IsArgSet("-tor"))) { - proxyType addrOnion; - if (!gArgs.IsArgSet("-tor")) { - addrOnion = addrProxy; + if (gArgs.IsArgSet("-tor")) { + CService addrOnion; + + // If -tor is specified without any argument, and proxy was specified, then override proxy with tor + // at same address and port. + if (gArgs.GetArg("-tor", "") == "") { + if (fProxy) { + addrOnion = addrProxy; + } } else { - CService addrProxy(LookupNumeric(gArgs.GetArg("-tor", "").c_str(), 9050)); + addrOnion = CService(LookupNumeric(gArgs.GetArg("-tor", "").c_str(), 9050)); + } + + if (!addrOnion.IsValid()) { + return InitError(strprintf(_("Invalid -tor address: '%s'"), gArgs.GetArg("-tor", gArgs.GetArg("-proxy", "")))); } - if (!addrOnion.IsValid()) - return InitError(strprintf(_("Invalid -tor address: '%s'"), gArgs.GetArg("-tor", ""))); SetProxy(NET_TOR, addrOnion); SetReachable(NET_TOR, true); } diff --git a/src/miner.cpp b/src/miner.cpp index 1c45764078..bc0ef2deb7 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -479,7 +479,6 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, - 2) * coinstake_output_ser_size; } - uint64_t nBlockTx = 0; int nBlockSigOps = 100; std::make_heap(vecPriority.begin(), vecPriority.end()); @@ -626,7 +625,6 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, block.vtx.push_back(tx); nBlockSize += nTxSize; - ++nBlockTx; nBlockSigOps += nTxSigOps; nFees += nTxFees; @@ -939,7 +937,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake CScript SideStakeScriptPubKey; GRC::Allocation SumAllocation; - // Lambda for sidestake allocation. This iterates throught the provided sidestake vector until either all elements processed, + // Lambda for sidestake allocation. This iterates through the provided sidestake vector until either all elements processed, // the maximum number of sidestake outputs is reached via the provided output_limit, or accumulated allocation will exceed 100%. const auto allocate_sidestakes = [&](SideStakeAlloc sidestakes, unsigned int output_limit) { for (auto iterSideStake = sidestakes.begin(); @@ -970,7 +968,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake if (allocation * nReward < CENT) { LogPrintf("WARN: SplitCoinStakeOutput: distribution %f too small to address %s.", - CoinToDouble(static_cast(allocation * nReward).ToCAmount()), + CoinToDouble((allocation * nReward).ToCAmount()), address.ToString() ); continue; @@ -1000,7 +998,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake // For allocations ending less than 100% assign using sidestake allocation. if (SumAllocation + allocation < 1) - nSideStake = static_cast(allocation * nReward).ToCAmount(); + nSideStake = (allocation * nReward).ToCAmount(); // We need to handle the final sidestake differently in the case it brings the total allocation up to 100%, // because testing showed in corner cases the output return to the staking address could be off by one Halford. else if (SumAllocation + allocation == 1) @@ -1012,7 +1010,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake LogPrintf("SplitCoinStakeOutput: create sidestake UTXO %i value %f to address %s", nOutputsUsed, - CoinToDouble(static_cast(allocation * nReward).ToCAmount()), + CoinToDouble((allocation * nReward).ToCAmount()), address.ToString() ); SumAllocation += allocation; diff --git a/src/net.cpp b/src/net.cpp index e6e26e5d90..0f1f87cf0d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1560,14 +1560,12 @@ void ThreadOpenConnections2(void* parg) // Only connect out to one peer per network group (/16 for IPv4). // Do this here so we don't have to critsect vNodes inside mapAddresses critsect. - int nOutbound = 0; set > setConnected; { LOCK(cs_vNodes); for (auto const& pnode : vNodes) { if (!pnode->fInbound) { setConnected.insert(pnode->addr.GetGroup()); - nOutbound++; } } } diff --git a/src/node/ui_interface.cpp b/src/node/ui_interface.cpp index c412d77269..297d64997a 100644 --- a/src/node/ui_interface.cpp +++ b/src/node/ui_interface.cpp @@ -67,7 +67,7 @@ ADD_SIGNALS_IMPL_WRAPPER(UpdateMessageBox); ADD_SIGNALS_IMPL_WRAPPER(RwSettingsUpdated); void CClientUIInterface::ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style); } -void CClientUIInterface::UpdateMessageBox(const std::string& version, const std::string& message) { return g_ui_signals.UpdateMessageBox(version, message); } +void CClientUIInterface::UpdateMessageBox(const std::string& version, const int& update_type, const std::string& message) { return g_ui_signals.UpdateMessageBox(version, update_type, message); } bool CClientUIInterface::ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCaption) { return g_ui_signals.ThreadSafeAskFee(nFeeRequired, strCaption).value_or(false); } bool CClientUIInterface::ThreadSafeAskQuestion(std::string caption, std::string body) { return g_ui_signals.ThreadSafeAskQuestion(caption, body).value_or(false); } void CClientUIInterface::ThreadSafeHandleURI(const std::string& strURI) { return g_ui_signals.ThreadSafeHandleURI(strURI); } diff --git a/src/node/ui_interface.h b/src/node/ui_interface.h index a0917c573a..6a2aa852be 100644 --- a/src/node/ui_interface.h +++ b/src/node/ui_interface.h @@ -88,7 +88,7 @@ class CClientUIInterface ADD_SIGNALS_DECL_WRAPPER(ThreadSafeMessageBox, void, const std::string& message, const std::string& caption, int style); /** Update notification message box. */ - ADD_SIGNALS_DECL_WRAPPER(UpdateMessageBox, void, const std::string& version, const std::string& message); + ADD_SIGNALS_DECL_WRAPPER(UpdateMessageBox, void, const std::string& version, const int& update_type, const std::string& message); /** Ask the user whether they want to pay a fee or not. */ ADD_SIGNALS_DECL_WRAPPER(ThreadSafeAskFee, bool, int64_t nFeeRequired, const std::string& strCaption); diff --git a/src/noui.cpp b/src/noui.cpp index 033b04df56..188b7f8bba 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -21,7 +21,7 @@ static bool noui_ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCa return true; } -static int noui_UpdateMessageBox(const std::string& version, const std::string& message) +static int noui_UpdateMessageBox(const std::string& version, const int& upgrade_type, const std::string& message) { std::string caption = _("Gridcoin Update Available"); diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index b62d55953b..9a763d4e37 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -71,6 +71,7 @@ add_library(gridcoinqt STATIC transactionrecord.cpp transactiontablemodel.cpp transactionview.cpp + updatedialog.cpp upgradeqt.cpp voting/additionalfieldstableview.cpp voting/additionalfieldstablemodel.cpp diff --git a/src/qt/aboutdialog.cpp b/src/qt/aboutdialog.cpp index b8707c941c..0f18d289d4 100755 --- a/src/qt/aboutdialog.cpp +++ b/src/qt/aboutdialog.cpp @@ -2,15 +2,44 @@ #include "qt/decoration.h" #include "ui_aboutdialog.h" #include "clientmodel.h" +#include "updatedialog.h" +#include "util.h" AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); - ui->copyrightLabel->setText("Copyright 2009-2024 The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"); + + QString copyrightText = "Copyright 2009-"; + std::variant copyright_year = COPYRIGHT_YEAR; + + try { + copyrightText += QString::number(std::get(copyright_year)); + } catch (const std::bad_variant_access& e) { + try { + copyrightText += std::get(copyright_year); + } catch (const std::bad_variant_access& e) { + copyrightText += "Present"; + } + } + + copyrightText += " The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"; + + ui->copyrightLabel->setText(copyrightText); resize(GRC::ScaleSize(this, width(), height())); + + if (!fTestNet && !gArgs.GetBoolArg("-disableupdatecheck", false)) { + connect(ui->versionInfoButton, &QAbstractButton::pressed, this, [this]() { handlePressVersionInfoButton(); }); + } else if (gArgs.GetBoolArg("-disableupdatecheck", false)) { + ui->versionInfoButton->setDisabled(true); + ui->versionInfoButton->setToolTip(tr("Version information and update check has been disabled " + "by config or startup parameter.")); + } else { + ui->versionInfoButton->setDisabled(true); + ui->versionInfoButton->setToolTip(tr("Version information is not available on testnet.")); + } } void AboutDialog::setModel(ClientModel *model) @@ -30,3 +59,28 @@ void AboutDialog::on_buttonBox_accepted() { close(); } + +void AboutDialog::handlePressVersionInfoButton() +{ + std::string client_message_out; + std::string change_log; + GRC::Upgrade::UpgradeType upgrade_type = GRC::Upgrade::UpgradeType::Unknown; + + + GRC::Upgrade::CheckForLatestUpdate(client_message_out, change_log, upgrade_type, false, false); + + if (client_message_out == std::string {}) { + client_message_out = "No response from GitHub - check network connectivity."; + change_log = " "; + } + + UpdateDialog update_dialog; + + update_dialog.setWindowTitle("Gridcoin Version Information"); + update_dialog.setVersion(QString().fromStdString(client_message_out)); + update_dialog.setUpgradeType(static_cast(upgrade_type)); + update_dialog.setDetails(QString().fromStdString(change_log)); + update_dialog.setModal(false); + + update_dialog.exec(); +} diff --git a/src/qt/aboutdialog.h b/src/qt/aboutdialog.h index 1b1dba84d7..a517b262b0 100644 --- a/src/qt/aboutdialog.h +++ b/src/qt/aboutdialog.h @@ -23,6 +23,7 @@ class AboutDialog : public QDialog private slots: void on_buttonBox_accepted(); + void handlePressVersionInfoButton(); }; #endif // BITCOIN_QT_ABOUTDIALOG_H diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index d9c91c51c3..fcc2edc1b7 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -42,7 +42,7 @@ class AddressBookPage : public QDialog const QString &getReturnValue() const { return returnValue; } public slots: - void done(int retval); + void done(int retval) override; void exportClicked(); void changeFilter(const QString& needle); void resizeTableColumns(const bool& neighbor_pair_adjust = false, const int& index = 0, diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 82bd5a8ab1..24028b6be3 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -192,16 +192,16 @@ static void InitMessage(const std::string &message) } } -static void UpdateMessageBox(const std::string& version, const std::string& message) +static void UpdateMessageBox(const std::string& version, const int& update_version, const std::string& message) { std::string caption = _("Gridcoin Update Available"); if (guiref) { - std::string guiaddition = version + _("Click \"Show Details\" to view changes in latest update."); QMetaObject::invokeMethod(guiref, "update", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), - Q_ARG(QString, QString::fromStdString(guiaddition)), + Q_ARG(QString, QString::fromStdString(version)), + Q_ARG(int, update_version), Q_ARG(QString, QString::fromStdString(message))); } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index aa7c86315f..37fa568a96 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -45,6 +45,7 @@ #include "upgradeqt.h" #include "voting/votingmodel.h" #include "voting/polltablemodel.h" +#include "updatedialog.h" #ifdef Q_OS_MAC #include "macdockiconhandler.h" @@ -1159,22 +1160,20 @@ void BitcoinGUI::error(const QString &title, const QString &message, bool modal) } } -void BitcoinGUI::update(const QString &title, const QString& version, const QString &message) +void BitcoinGUI::update(const QString &title, const QString& version, const int& upgrade_type, const QString &message) { - // Create our own message box; A dialog can go here in future for qt if we choose - - updateMessageDialog.reset(new QMessageBox); + updateMessageDialog.reset(new UpdateDialog); updateMessageDialog->setWindowTitle(title); - updateMessageDialog->setText(version); - updateMessageDialog->setDetailedText(message); - updateMessageDialog->setIcon(QMessageBox::Information); - updateMessageDialog->setStandardButtons(QMessageBox::Ok); + updateMessageDialog->setVersion(version); + updateMessageDialog->setUpgradeType(static_cast(upgrade_type)); + updateMessageDialog->setDetails(message); updateMessageDialog->setModal(false); - connect(updateMessageDialog.get(), &QMessageBox::finished, [this](int) { updateMessageDialog.reset(); }); + + connect(updateMessageDialog.get(), &QDialog::finished, this, [this]() { updateMessageDialog.reset(); }); + // Due to slight delay in gui load this could appear behind the gui ui // The only other option available would make the message box stay on top of all applications - QTimer::singleShot(5000, updateMessageDialog.get(), [this]() { updateMessageDialog->show(); }); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 5b1413cbbf..e1934f8348 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -32,6 +32,7 @@ class Notificator; class RPCConsole; class DiagnosticsDialog; class ClickLabel; +class UpdateDialog; QT_BEGIN_NAMESPACE class QLabel; @@ -110,7 +111,7 @@ class BitcoinGUI : public QMainWindow TransactionView *transactionView; VotingPage *votingPage; SignVerifyMessageDialog *signVerifyMessageDialog; - std::unique_ptr updateMessageDialog; + std::unique_ptr updateMessageDialog; QLabel *statusbarAlertsLabel; QLabel *labelEncryptionIcon; @@ -205,7 +206,7 @@ public slots: void setEncryptionStatus(int status); /** Notify the user if there is an update available */ - void update(const QString& title, const QString& version, const QString& message); + void update(const QString& title, const QString& version, const int& upgrade_type, const QString& message); /** Notify the user of an error in the network or transaction handling code. */ void error(const QString &title, const QString &message, bool modal); diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui index c67bdbac35..e3952591f1 100644 --- a/src/qt/forms/aboutdialog.ui +++ b/src/qt/forms/aboutdialog.ui @@ -136,6 +136,43 @@ This product includes software developed by the OpenSSL Project for use in the O + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Version Information + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/qt/forms/updatedialog.ui b/src/qt/forms/updatedialog.ui new file mode 100644 index 0000000000..d713ec8b28 --- /dev/null +++ b/src/qt/forms/updatedialog.ui @@ -0,0 +1,98 @@ + + + UpdateDialog + + + + 0 + 0 + 609 + 430 + + + + Dialog + + + + + + + + icon + + + + + + + version + + + + + + + + + false + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + changelog + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + UpdateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UpdateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 7082b578a5..f43dc85779 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -48,7 +48,7 @@ QString dateTimeStr(qint64 nTime) QString formatPingTime(double dPingTime) { - return (dPingTime == std::numeric_limits::max()/1e6 || dPingTime == 0) ? + return (dPingTime >= static_cast(std::numeric_limits::max()) / 1e6 || dPingTime <= 0) ? QObject::tr("N/A") : QObject::tr("%1 ms").arg(QString::number((int)(dPingTime * 1000), 10)); } diff --git a/src/qt/locale/bitcoin_de.ts b/src/qt/locale/bitcoin_de.ts index fb6bd84a62..dcc5012ea8 100644 --- a/src/qt/locale/bitcoin_de.ts +++ b/src/qt/locale/bitcoin_de.ts @@ -12,7 +12,11 @@ Value Wert - + + Required + Erforderlich + + AddressBookPage @@ -63,6 +67,10 @@ These are your Gridcoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. Das sind Ihre Gridcoin Adressen um Zahlungen zu erhalten. Sie werden vielleicht verschiedene an jeden Sender vergeben, damit Sie im Auge behalten können wer sie bezahlt. + + Double-click to edit label + Doppelklicken um das Label zu bearbeiten + &Delete &Löschen @@ -79,6 +87,11 @@ Export Address Book Data Exportiere Addressbuch Daten + + Comma separated file + Name of CSV file format + Kommaseparierte Datei + Error exporting Fehler berichten @@ -206,6 +219,13 @@ Warnung: Die Feststelltaste ist aktiviert! + + BanTableModel + + IP/Netmask + IP/Netzmaske + + BitcoinGUI @@ -236,6 +256,10 @@ Show the list of addresses for receiving payments Zeige die Liste der Addressen für die Erhaltung von Zahlungen + + &History + &Historie + Browse transaction history Transaktionsverlauf durchsehen @@ -264,6 +288,14 @@ &Web Site &Webseite + + &GRC Chat Room + &GRC Chatraum + + + GRC Chatroom + GRC Chatraum + Gridcoin rewards distributed computing with BOINC Gridcoin belohnt verteiltest Rechnen mit BOINC @@ -308,6 +340,10 @@ &Encrypt Wallet... Wallet &verschlüsseln... + + Encrypt wallet + Wallet verschlüsseln + &Change Passphrase... Passphrase &ändern... @@ -368,6 +404,10 @@ &Help &Hilfe + + Toggle light/dark mode. + Hell-/Dunkelmodus umschalten. + [testnet] [Testnetz] @@ -446,6 +486,14 @@ Incoming transaction Eingehende Transaktion + + Close Confirmation + Beenden bestätigen + + + Exit the Gridcoin wallet? + Die Gridcoinwallet beenden? + URI handling URI Handhabung @@ -514,6 +562,10 @@ %1 times per %2 %1 Mal pro %2 + + none + keine + ClientModel @@ -552,6 +604,10 @@ Change: Wechselgeld: + + Select All + Alles auswählen + Amount Betrag @@ -682,13 +738,45 @@ ConsolidateUnspentWizardSelectInputsPage + + Address + Adresse + Date Datum + + Confirmations + Bestätigungen + + + Confirmed + Bestätigt + + + Quantity + Anzahl + + + Fee + Gebühr + DiagnosticsDialog + + Diagnostics + Diagnose + + + Close + Schließen + + + Passed + Bestanden + Warning Warnung @@ -777,6 +865,10 @@ Welcome Willkommen + + Error + Fehler + %n GB of free space available @@ -805,6 +897,14 @@ Form Formular + + Nothing here yet... + Noch nichts hier... + + + No results available. + Keine Ergebnisse verfügbar. + Loading... Lade... @@ -852,6 +952,14 @@ Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. Minimiert die Anwendung anstatt sie zu beenden wenn das Fenster geschlossen wird. Wenn dies aktiviert ist, müssen Sie das Programm über "Beenden" im Menü schließen. + + Disable Transaction Notifications + Transaktionsbenachrichtigungen deaktivieren + + + Disable Poll Notifications + Abstimmungsbenachrichtigungen deaktivieren + The user interface language can be set here. This setting will take effect after restarting Gridcoin. Die Sprache der GUI kann hier verändert werden. Die Einstellung wird nach einem Neustart übernommen. @@ -876,6 +984,10 @@ Show only a tray icon after minimizing the window. Nur ein Symbol im Infobereich anzeigen, nachdem das Programmfenster minimiert wurde. + + Start minimized + Minimiert starten + &Minimize to the tray instead of the taskbar In den Infobereich anstatt in die Taskleiste &minimieren @@ -976,6 +1088,17 @@ Schwierigkeit: + + PeerTableModel + + Sent + Gesendet + + + Received + Empfangen + + PollCard @@ -1535,7 +1658,11 @@ WARNING: unknown change address WARNUNG: Unbekannte Wechseladdresse - + + Active + Aktiv + + SendCoinsEntry @@ -2018,12 +2145,23 @@ bis + + VotingModel + + Poll not found. + Abstimmung nicht gefunden. + + VotingPage Sort by... Sortieren nach... + + &Active + &Aktiv + WalletModel diff --git a/src/qt/locale/bitcoin_pt_PT.ts b/src/qt/locale/bitcoin_pt_PT.ts index 3737f96cad..d3c4bcc10b 100644 --- a/src/qt/locale/bitcoin_pt_PT.ts +++ b/src/qt/locale/bitcoin_pt_PT.ts @@ -316,11 +316,11 @@ Este produto inclui desenvolvimento de software pelo Projeto OpenSSL para uso em Web Site - Website + Sítio Web &Web Site - &Website + &Site Web &GRC Chat Room @@ -460,11 +460,11 @@ Este produto inclui desenvolvimento de software pelo Projeto OpenSSL para uso em &Reset blockchain data - &Repor dados da blockchain + &Repor dados da cadeia de blocos Remove blockchain data and start chain from zero - Remover dados da blockchain e começar a cadeira do zero + Remover dados da cadeia de blocos e começar a cadeia do zero &Mask values @@ -608,15 +608,15 @@ Endereço: %4 Warning: Canceling after stage 2 will result in sync from 0 or corrupted blockchain files. - Aviso: Cancelar depois do 2º passo irá resultar em sincronizar tudo do "0", ou ficheiros corrompidos no blockchain. + Aviso: Cancelar depois do 2º passo irá resultar em sincronizar tudo do "0", ou ficheiros corrompidos na cadeia de blocos. Do you want to delete blockchain data and sync from zero? - Tem a certeza que quer eliminar os dados da blockchain e começar a sincronização do zero? + Tem a certeza que quer eliminar os dados da cadeia de blocos e começar a sincronização do zero? Warning: After the blockchain data is deleted, the wallet will shutdown and when restarted will begin syncing from zero. Your balance will temporarily show as 0 GRC while syncing. - Aviso: Depois dos dados da blockchain serem eliminados, a carteira irá encerrar e ao reiniciar, irá começar a sincronizar do zero. O seu balanço irá temporariamente aparecer como 0 GRC enquanto sincroniza. + Aviso: Depois dos dados da cadeia de blocos serem eliminados, a carteira irá encerrar e ao reiniciar, irá começar a sincronizar do zero. O seu balanço irá temporariamente aparecer como 0 GRC enquanto sincroniza. Close Confirmation @@ -754,6 +754,12 @@ Projeto(s) excluído(s): %2. CPID: %1 +Time left to activate: %2%3 + CPID: %1 +Tempo restante para ativar: %2%3 + + + CPID: %1 Beacon age: %2 Current beacon expired! %3 @@ -774,11 +780,11 @@ Expira: %3 New Poll - Nova Votação + Nova Sondagem A new poll is available. Open Gridcoin to vote. - Uma nova votação está disponível. Abra o Gridcoin para votar. + Uma nova sondagem está disponível. Abra o Gridcoin para votar. @@ -850,6 +856,10 @@ Expira: %3 Filter Filtro + + Pushing this button after making a input selection either manually or with the filter will present a destination address list where you specify a single address as the destination for the consolidated output. The send (Pay To) entry will be filled in with this address and you can finish the consolidation by pressing the send button. + Ao premir este botão depois de fazer uma seleção de entrada manualmente ou com o filtro, é apresentada uma lista de endereços de destino onde pode especificar um único endereço como destino para a saída consolidada. A entrada de envio (Pagar a) será preenchida com este endereço e pode terminar a consolidação premindo o botão de enviar. + Consolidate Consolidado @@ -930,6 +940,10 @@ Expira: %3 Copy change Copiar modificação + + Flips the filter mode between selecting inputs less than or equal to the provided value (<=) and greater than or equal to the provided value (>=). The filter also automatically limits the number of inputs to %1, in ascending order for <= and descending order for >=. + Alterna o modo de filtro entre a seleção de entradas menores ou iguais ao valor fornecido (<=) e maiores ou iguais ao valor fornecido (>=). O filtro também limita automaticamente o número de entradas a %1, em ordem crescente para <= e decrescente para >=. + Select None Não Selecionar Nenhum @@ -1027,6 +1041,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. WizardPage Página do Assistente + + Step 2: Select the destination address for the consolidation transaction. Note that all of the selected inputs will be consolidated to an output on this address. If there is a very small amount of change (due to uncertainty in the fee calculation), it will also be sent to this address. If you selected inputs only from a particular address on the previous page, then that address will already be selected by default. + Passo 2: Selecionar o endereço de destino para a transação de consolidação. Note que todas as entradas selecionadas serão consolidadas numa saída neste endereço. Se houver um valor muito pequeno de mudança (devido à incerteza no cálculo da taxa), ele também será enviado para este endereço. Se na página anterior tiver selecionado apenas entradas de um determinado endereço, esse endereço já estará selecionado por defeito. + Label Etiqueta @@ -1270,11 +1288,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. One or more tests have generated a warning status. Wallet operation may be degraded. Please see the individual test tooltips for details and recommended action(s). - Um ou mais testes geraram um estado de alerta. Operações com a carteira podem estar corrompidas. Por favor, veja as dicas individuais de teste para detalhes e ações recomendadas. + Um ou mais testes geraram um estado de alerta. Operações com a carteira podem estar corrompidas. Por favor, consulte as dicas individuais de teste para detalhes e ações recomendadas. One or more tests have failed. Proper wallet operation may be significantly degraded or impossible. Please see the individual test tooltips for details and recommended action(s). - Um ou mais testes falharam. Operações adequadas com a carteira, podem estar signifcamente degradadas ou impossiveis. Por favor, veja as dicas indivuais de teste para detalhes e ações recomendadas. + Um ou mais testes falharam. Operações adequadas com a carteira, podem estar significativamente degradadas ou impossíveis. Por favor, consulte as dicas individuais de teste para detalhes e ações recomendadas. All tests passed. Your wallet operation is normal. @@ -1410,6 +1428,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Use a custom data directory: Utilizar uma diretoria de dados personalizada: + + When you click OK, %1 will begin to download and process the full %4 block chain (~%2GB), either from genesis in %3, or the last synchronized block, if this was a preexisting data directory. + Quando clicar em OK, %1 começará a transferir e a processar toda a cadeia de blocos %4 (~%2GB), quer a partir da génese em %3, ou a partir do último bloco sincronizado, se este for um diretório de dados pré-existente. + + + The synchronization is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off. + A sincronização é muito exigente e pode expor problemas de hardware do seu computador que anteriormente tivessem passado despercebidos. Cada vez que executar o %1, ele continuará a transferência onde parou anteriormente. + Error: Specified data directory "%1" cannot be created. Erro: Diretoria de dados especificada "%1" não pode ser criada. @@ -1442,10 +1468,34 @@ Isto significa que uma taxa de pelo menos %2 é necessária. MRCModel + + You must have a mature balance of at least 1 GRC to submit an MRC. + Tem que ter um balanço com maturidade de pelo menos 1 GRC para poder submeter um pedido de MRC. + + + Too soon since your last research rewards payment. + Muito cedo desde o último pagamento de recompensa de pesquisa. + + + The total fee (the minimum fee + fee boost) is greater than the rewards due. + A taxa total (taxa mínima + taxa de boost) é maior que os ganhos da recompensa. + + + Your MRC was successfully submitted earlier but has now become stale without being bound to the just received block by a staker. This may be because your MRC was submitted just before the block was staked and the MRC didn't make it to the staker in time, or your MRC was pushed down in the queue past the pay limit. Please wait for the next block to clear the queue and try again. + O seu MRC foi submetido com sucesso mais cedo, mas agora tornou-se obsoleto sem estar ligado ao bloco recém recebido por um staker. Isto pode acontecerr porque o seu MRC foi submetida pouco antes do bloco ter sido staked e o MRC não chegou a tempo ao staker, ou o seu MRC desceu na fila para além do limite de pagamentos. Por favor, aguarde pelo próximo bloco para limpar a fila de espera e tente novamente. + You have a pending MRC request. Tem uma solicitação de MRC pendente. + + Your MRC was successfully submitted, but other MRCs with higher fees have pushed your MRC down in the queue past the pay limit, and your MRC will be canceled. Wait until the next block is received and the queue clears and try again. Your fee for the canceled MRC will be refunded. + O seu MRC foi submetido com êxito, mas outros MRCs com taxas mais altas, fizeram o seu MRC baixar na fila, ultrapassando o limite de pagamentos, e o seu MRC será cancelado. Aguarde até que o próximo bloco seja recebido e a fila seja limpa e tente novamente. A sua taxa para o MRC cancelado será reembolsada. + + + The MRC queue is full. You can try boosting your fee to put your MRC request in the queue and displace another MRC request. + A fila do MRC está cheia. Pode tentar aumentar a sua taxa para colocar o seu pedido MRC na fila e remover da fila outro pedido de MRC. + The wallet is locked. A carteira está trancada. @@ -1465,6 +1515,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. MRC Fee @ Pay Limit Position in Queue Taxa MRC @ Posição Limite de Pagamento na Fila + + MRC Fee @ Tail of Queue + Taxa MRC @ Na Cauda da Fila + + + Your projected or actual position among MRCs in the memory pool ordered by MRC fee in descending order + A sua posição projetada ou real entre os MRCs na reserva de memória, ordenada por taxa de MRC por ordem decrescente + Number of All MRC Requests in Queue Número de Todos os Pedidos MRC na Fila @@ -1473,6 +1531,50 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The number of MRCs in the memory pool Número de pedidos MRC em espera + + Your Projected MRC Request Position in Queue + A sua posição prevista do seu pedido MRC na fila de espera + + + The MRC fee being paid by the MRC in the last position within the pay limit in the memory pool + A taxa de MRC que está a ser paga pelo MRC na última posição dentro do limite de remuneração na reserva de memória + + + MRC Request Pay Limit per Block + Limite de pagamento por bloco do pedido MRC + + + Your MRC Calculated Minimum Fee + A sua taxa mínima calculada pelo MRC + + + The calculated minimum fee for the MRC. This may not be sufficient to submit the MRC if the queue is already full. In that case, you need to use the MRC fee boost to raise the fee to get your MRC in the queue. + A taxa mínima calculada para o MRC. Este valor pode não ser suficiente para submeter o MRC se a fila de espera já estiver cheia. Nesse caso, é necessário que faça um boost da taxa de MRC para a aumentar, colocando o MRC na fila de espera. + + + The lowest MRC fee being paid of MRCs in the memory pool + A taxa mais baixa de MRC que está a ser paga de MRCs na reserva de memória + + + The maximum number of MRCs that can be paid per block + O número máximo de MRCs que pode ser pago por bloco + + + The highest MRC fee being paid of MRCs in the memory pool + A taxa mais alta de MRC que está a ser paga de MRCs na reserva de memória + + + MRC Fee @ Head of Queue + Taxa MRC @ No Topo da Fila + + + MRC Fee Boost + Aumento da taxa de inscrição no MRC + + + Raise to Minimum For Submit + Subir para o Mínimo para Submeter + Update Atualizar @@ -1501,7 +1603,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A block update must have occurred after wallet start or sync to submit MRCs. Uma atualização do bloco tem de ocorrer depois da carteira iniciar ou sincronizar, para submeter um pedido MRC. - + + You must have a mature balance of at least 1 GRC to submit an MRC. + Tem que ter um balanço com maturidade de pelo menos 1 GRC para poder submeter um pedido de MRC. + + NoResult @@ -1583,9 +1689,33 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Staking A realizar stake + + This enables or disables staking (the default is enabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + Isto ativa ou desativa o staking (por predefinição está ativado). Note que uma alteração a esta definição irá substituir permanentemente o ficheiro de configuração por uma entrada no ficheiro de definições. + Enable Staking - Habilitar Realização de Stake + Ativar Staking + + + This enables or disables splitting of stake outputs to optimize staking (default disabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + Isso permite ativar ou desativar a divisão das saídas de stake para otimizar a aposta (desativado por padrão). Note que uma alteração nesta configuração irá substituir permanentemente o arquivo de configuração com uma entrada no ficheiro de configurações. + + + Enable Stake Splitting + Ativar Divisão de Stake + + + Target Efficiency + Eficiência do Objetivo + + + Min Post Split UTXO + Mínimo Pós-divisão UTXO + + + Valid values are 800 or greater. Note that a change to this setting will permanently override the config file with an entry in the settings file. + Os valores válidos são 800 ou superior. Note que uma alteração a esta definição substituirá permanentemente o ficheiro de configuração por uma entrada no ficheiro de definições. Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. @@ -1601,7 +1731,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Disable Poll Notifications - Desabilitar Notificações de Votações + Desabilitar Notificações de Sondagens The user interface language can be set here. This setting will take effect after restarting Gridcoin. @@ -1647,6 +1777,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Disable &update checks Desabilitar verificações de &atualização + + Return change to an input address for contract transactions + Devolver o troco para um endereço de entrada para transações de contrato + + + Connect to the Gridcoin network through a SOCKS5 proxy (e.g. when connecting through Tor). + Ligar à rede Gridcoin através de um proxy SOCKS5 (por exemplo, quando a ligação é feita através do Tor). + &Connect through SOCKS5 proxy: &Ligar através de proxy SOCKS5: @@ -1711,7 +1849,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The supplied proxy address is invalid. O endereço de proxy introduzido é inválido. - + + The supplied target staking efficiency is invalid. + A eficiência de staking do alvo fornecido é inválida. + + + The supplied minimum post stake-split UTXO size is invalid. + O tamanho mínimo de UTXO pós-divisão de stack fornecido é inválido. + + OverviewPage @@ -1806,17 +1952,25 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Researcher Investigador + + Open the Manual Reward Claim (MRC) request page + Abrir a página de pedido de Pedido de Recompensa Manual (MRC) + Status: Estado: + + You are approaching the accrual limit of 16384 GRC. If you have a relatively low balance, you should request payment via MRC so that you do not lose earned rewards. + Está a atingir o limite de acumulação de 16384 GRC. Se tiver um saldo relativamente baixo, deve solicitar o pagamento através de MRC para que não perca as recompensas ganhas. + Recent Transactions Transações Recentes Current Polls - Votações Atuais + Sondagens Atuais Out of Sync @@ -1874,7 +2028,19 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll Type - Tipo de Votação + Tipo de Sonadagem + + + Your Last Vote: + O Seu Último Voto: + + + Your Vote Weight: + O Peso do Seu Voto: + + + Your % of AVW: + A Sua % de AVW Balance @@ -1938,11 +2104,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollResultDialog Poll Details - Detalhes da Votação + Detalhes da Sondagem Poll ID - ID da Votação + ID da Sondagem @@ -1976,7 +2142,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll Type - Tipo de Votação + Tipo de Sondagem Duration @@ -2015,22 +2181,22 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizard Create a Poll - Criar uma Votação + Criar uma Sondagem PollWizardDetailsPage Poll Details - Detalhes da Votação + Detalhes da Sondagem Some fields are locked for the selected poll type. - Alguns campos estão bloqueados para o tipo de votação selecionada. + Alguns campos estão bloqueados para o tipo de sondagem selecionada. Poll Type: - Tipo de Votação: + Tipo de Sondagem: Duration: @@ -2070,7 +2236,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A poll with a yes/no/abstain response type cannot include any additional custom choices. - Uma votação com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. + Uma sondagem com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. Additional Fields: @@ -2078,7 +2244,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Create Poll - Criar Votação + Criar Sondagem Balance @@ -2102,7 +2268,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. This poll will cost %1 plus a transaction fee. Continue? - Esta votação irá custar %1 para além duma taxa adicional. Continuar? + Esta sondagem irá custar %1 para além duma taxa adicional. Continuar? @@ -2144,11 +2310,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizardSummaryPage Poll Created - Votação Criada + Sondagem Criada The poll will activate with the next block. - A votação será ativada no próximo bloco. + A sondagem será ativada no próximo bloco. Copy ID @@ -2159,15 +2325,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizardTypePage Create a Poll - Criar uma Votação + Criar uma Sondagem The Gridcoin community established guidelines for polls with requirements for each type. Please read the wiki for more information: - A comunidade Gridcoin estabeleceu diretrizes para as votações, com requisitos para cada tipo. Por favor leia a wiki para mais informações: + A comunidade Gridcoin estabeleceu diretrizes para as sondagem, com requisitos para cada tipo. Por favor leia a wiki para mais informações: Choose a poll type: - Escolha o tipo de votação: + Escolha o tipo de sondagem: @@ -2195,6 +2361,17 @@ Isto significa que uma taxa de pelo menos %2 é necessária. QObject + + Error: Cannot parse command line arguments. Please check the arguments and ensure they are valid and formatted correctly: + + + Erro: Não é possível analisar os argumentos na linha de comando. Verifique os argumentos e certifique-se de que são válidos e estão formatados corretamente: + + + + Error: Cannot read configuration file. Please check the path and format of the file. + Erro: Não é possível ler o ficheiro de configuração. Verifique o caminho e o formato do ficheiro. + Error: Specified data directory "%1" does not exist. Erro: A diretoria %1 especificada não existe. @@ -2674,7 +2851,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Not attached Não anexado - + + Uses external adapter + Utiliza adaptador externo + + ResearcherWizard @@ -2698,12 +2879,16 @@ Isto significa que uma taxa de pelo menos %2 é necessária. 1. Sign in to your account at the website for a whitelisted BOINC project. - 1. Entre na sua conta num website dum projeto BOINC na lista aprovada. + 1. Entre na sua conta no sítio web dum projeto BOINC que esteja na lista aprovada. 2. Visit the settings page to change your username. Many projects label it as "other account info". 2. Visite a página de configurações para alterar o nome de utilizador. Muitos projetos identificam como "outra informação da conta" + + 3. Change your "name" (real name or nickname) to the following verification code: + 3. Altere o seu "nome" (nome real ou alcunha) para o seguinte código de verificação: + Copy the verification code to the system clipboard Copie o código de verificação para a área de transferência @@ -2714,7 +2899,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. 4. Some projects will not export your statistics by default. If available, enable the privacy setting that gives consent to the project to export your statistics data. Many projects place this setting on the "Preferences for this Project" page and label it as "Do you consent to exporting your data to BOINC statistics aggregation web sites?" - 4. Alguns projetos não irão exportar as suas estatísticas por defeito. Se a opção estiver disponível, deve alterar as definições de privacidade que consentem que um projeto possa exportar os dados das suas estatísticas. Muitos projetos colocam esta definição na página "Preferências para este Projeto" e etiquetam-na como "Pretende consentir a exportação dos seus dados para websites de estatísticas de agregação do BOINC ?" + 4. Alguns projetos não irão exportar as suas estatísticas por defeito. Se a opção estiver disponível, deve alterar as definições de privacidade que consentem que um projeto possa exportar os dados das suas estatísticas. Muitos projetos colocam esta definição na página "Preferências para este Projeto" e etiquetam-na como "Pretende consentir a exportação dos seus dados para sítios web de estatísticas de agregação do BOINC ?" 5. Wait 24 to 48 hours for the verification process to finish (beacon status will change to "active"). @@ -2911,11 +3096,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. In this mode, a pool will take care of staking research rewards for you. Your wallet can still earn standard staking rewards on your balance. You do not need a BOINC account, CPID, or beacon. Please choose a pool and follow the instructions on the website to sign up and connect the pool's account manager to BOINC: - Neste modo, a pool irá tomar conta de realizar o stake para pesquisar recompensas para si. A sua carteira pode ainda ganhar recompensas normais por realizar stake. Não precisa de uma conta BOINC, CPID ou Beacon. Por favor escolha uma pool e siga as instruções no website para se registar e ligar a uma conta duma pool gestora do BOINC. + Neste modo, a pool irá tomar conta de realizar o stake para pesquisar recompensas para si. A sua carteira pode ainda ganhar recompensas normais por realizar stake. Não precisa de uma conta BOINC, CPID ou Beacon. Por favor escolha uma pool e siga as instruções no sítio web para se registar e ligar a uma conta duma pool gestora do BOINC: Website URL - URL do Website + URL do Sítio Web As you sign up, the pool may ask for a payment address to send earnings to. Press the button below to generate an address. @@ -2945,7 +3130,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Pool Receiving Address Endereço de Receção da Pool - + + Error: failed to generate a new address. + Erro: falhou ao gerar um novo endereço. + + ResearcherWizardPoolSummaryPage @@ -3094,6 +3283,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Beacon renewal available. Renovação do beacon disponível. + + Split CPID or mismatched email. + CPID dividido ou eMail não correspondente. + + + Your projects either refer to more than one CPID or your projects' email do not match what you used to configure Gridcoin here. Please ensure all of your projects are attached using the same email address, the email address matches what was configured here, and if you added a project recently, update that project and then all other projects using the update button in the BOINC manager, then restart the client and recheck. + Os seus projetos referem-se a mais do que um CPID ou, o e-mail dos seus projetos não corresponde ao que utilizou para configurar o Gridcoin. Por favor, certifique-se de que todos os seus projetos estão anexados utilizando o mesmo endereço de e-mail, o que corresponde ao que foi configurado aqui, e se adicionou um projeto recentemente, atualize esse projeto de seguida, todos os outros projetos utilizando o botão de atualização no Gestor BOINC. Por último, reinicie o cliente e verifique novamente. + Waiting for magnitude. Aguardando pela magnitude @@ -3137,6 +3334,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reset Repor + + Consolidate Wizard + Assistente de Consolidação + Quantity: Quantidade: @@ -3503,9 +3704,9 @@ Isto significa que uma taxa de pelo menos %2 é necessária. , broadcast through %n node(s) - - - + + , transmitida através de %n nó + , transmitida através de %n nós @@ -3548,6 +3749,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PoS+RR Side Stake Sent PoS+RR Side Stake Enviado + + MRC Payment Received + Pagamento MRC Recebido + + + MRC Payment Sent + Pagamento MRC Enviado + Mined - Superblock Minada - Super Bloco @@ -3768,6 +3977,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PoS+RR Side Stake Sent PoS+RR Side Stake Enviado + + MRC Payment Received + Pagamento MRC Recebido + + + MRC Payment Sent + Pagamento MRC Enviado + Mined - Superblock Minada - Super Bloco @@ -3782,12 +3999,16 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll - Votação + Sondagem Vote Voto + + Manual Rewards Claim Request + Solicitação Manual de Reivindicação de Recompensas + Message Mensagem @@ -4009,11 +4230,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll not found. - Votação não encontrada. + Sondagem não encontrada. Failed to load poll from disk - Falhou o carregamento das votações do disco + Falhou o carregamento das sondagens do disco @@ -4024,7 +4245,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Polls - Votações + Sondagem Search by title @@ -4048,11 +4269,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Create &Poll - Criar &Votação + Criar &Sondagem A new poll is available. Press "Refresh" to load it. - Uma nova votação está disponível. Carregue em "Atualizar" para a mostrar. + Uma nova sondagem está disponível. Carregue em "Atualizar" para a mostrar. &Active @@ -4090,7 +4311,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A poll with a yes/no/abstain response type cannot include any additional custom choices. - Uma votação com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. + Uma sondagem com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. Cannot obtain a lock on data directory %s. %s is probably already running and using that directory. @@ -4100,6 +4321,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Cannot obtain a lock on data directory %s. %s is probably already running. Não foi possivel cadear a diretoria de dados %s. %s já está provavelmente em execução. + + Check that BOINC is installed and that you have the correct path in the config file if you installed it to a nonstandard location. + Verifique que o BOINC está instalado e que tem o caminho correto no ficheiro de configuração, caso o tenha instalado numa localização não definida por padrão. + + + Error: Clock skew is 5 minutes or greater. Please check your clock settings. + Erro: O sincronização do relógio é de 5 minutos ou mais. Verifique as definições do seu relógio. + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. Erro: A transação foi rejeitada. Isso pode acontecer se algumas das na sua moedas na carteira já tiverem sido gastas, se tiver utilizado uma cópia da wallet.dat e as moedas não tiverem sido marcadas como gastas aqui. @@ -4108,21 +4337,153 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds Erro: Esta transação devido à sua quantia, complexidade ou utilização de fundos recebidos recentemente, necessita de uma taxa de transação de pelo menos %s + + For initiatives related to the Gridcoin community not covered by other poll types. + Para iniciativas relacionadas com a Comunidade Gridcoin, não abrangidas por outros tipos de sondagem. + + + For polls about community representation, public relations, and communications. + Para sondagens sobre representação comunitária, relações públicas e comunicações. + + + Please check your network and also check the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + Por favor, verifique a sua rede e também o ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, poderá querer aguardar mais alguns minutos para que as ligações sejam estabelecidas e testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Please ensure that you have followed the process to advertise and verify your beacon. You can use the research wizard (the beacon button on the overview screen). + Certifique-se de que seguiu o processo para publicitar e verificar o seu beacon. Pode utilizar o assistente de pesquisa (o botão beacon no ecrã de resumo). + + + Poll additional field value "%s" for field name "%s" exceeds %s characters. + A sondagem do valor de campo adicional "%s" para o nome de campo "%s" excede os %s caracteres. + + + Proposals related to Gridcoin management like poll requirements and funding. + Propostas relacionadas com a gestão do Gridcoin, como requisitos de sondagem e financiamento. + + + Propose additions or removals of computing projects for research reward eligibility. + Propor adições ou remoções de projetos de computação para a elegibilidade de prémios de investigação. + + + The IP for the port test site is unable to be resolved. This could mean your DNS is not working correctly. The wallet may operate without DNS, but it could be severely degraded, especially if the wallet is new and a database of prior successful connections has not been built up. Please check your computer and ensure name resolution is operating correctly. + Não é possível resolver o IP do site de teste de portas. Isto pode significar que o seu DNS não está a funcionar corretamente. A carteira pode funcionar sem DNS, mas pode ser gravemente degradada, especialmente se a carteira for nova e não tiver sido criada uma base de dados de ligações anteriores bem sucedidas. Verifique o seu computador e certifique-se de que a resolução de nomes está a funcionar corretamente. + + + The connection to the port test site was refused. This could be a transient problem with the port test site, but could also be an issue with your firewall. If you are also failing the connection test, your firewall is most likely blocking network communications from the Gridcoin client. + A ligação ao site de teste de portas foi recusada. Isto pode ser um problema transitório com o site de teste de portas, mas também pode ser um problema com a sua firewall. Se também estiver a falhar o teste de ligação, a sua firewall está provavelmente a bloquear as comunicações de rede do cliente Gridcoin. + + + The network has experienced a low-level error and this probably means your IP address or other network connection parameters are not configured correctly. Please check your network configuration on your computer. + A rede sofreu um erro de baixo nível, o que provavelmente significa que o seu endereço IP ou outros parâmetros de ligação à rede não estão corretamente configurados. Verifique a configuração da rede no seu computador. + + + The network is reporting an unspecified socket error. If you also are failing the connection test, then please check your computer's network configuration. + A rede está a reportar um erro de socket não especificado. Se também falhar o teste de ligação, verifique a configuração de rede do seu computador. + + + The port test site is closed on port. This could be a transient problem with the port test site, but could also be an issue with your firewall. If you are also failing the connection test, your firewall is most likely blocking network communications from the Gridcoin client. + O sítio de teste de portas está fechado na porta. Isto pode ser um problema transitório com o site de teste de portas, mas também pode ser um problema com a sua firewall. Se também estiver a falhar o teste de ligação, a sua firewall está muito provavelmente a bloquear as comunicações de rede do cliente Gridcoin. + + + The wallet has less than five connections to the network and is unable to connect to an NTP server to check your computer clock. This is not necessarily a problem. You can wait a few minutes and try the test again. + A carteira tem menos de cinco ligações à rede e não consegue ligar-se a um servidor NTP para verificar o relógio do seu computador. Isto não é necessariamente um problema. Pode esperar alguns minutos e repetir o teste novamente. + The wallet will now shutdown. Please start your wallet to begin sync from zero A carteira irá encerrar. Por favor, inicie a sua carteira para começar a sincronização do zero + + There is a new leisure version available and you should upgrade as soon as practical. + Existe uma nova versão de lazer disponível e deve ser atualizada assim que possível. + + + There is a new mandatory version available and you should upgrade as soon as possible to ensure your wallet remains in consensus with the network. + Existe uma nova versão obrigatória disponível e deve atualizá-la o mais rapidamente possível para garantir que a sua carteira se mantém em consenso com a rede. + + + Verify (1) that you have BOINC installed correctly, (2) that you have attached at least one whitelisted project, (3) that you advertised your beacon with the same email as you use for your BOINC project(s), and (4) that the CPID on the overview screen matches the CPID when you login to your BOINC project(s) online. + Verifique (1) se o BOINC está instalado corretamente, (2) se adicionou pelo menos um projeto da whitelist, (3) se anunciou seu beacon com o mesmo e-mail que usa para o(s) seu(s) projeto(s) BOINC e (4) se o CPID no ecrã de visão geral corresponde ao CPID quando inicia sessão no(s) seu(s) projeto(s) BOINC on-line. + + + Verify that you have actually completed workunits for the projects you have attached and that you have authorized the export of statistics. Please see https://gridcoin.us/guides/whitelist.htm. + Verifique se as unidades de trabalho dos projetos que adicionou foram efetivamente concluídas e se autorizou a exportação de estatísticas. Consultar https://gridcoin.us/guides/whitelist.htm. + WARNING: A mandatory release is available. Please upgrade as soon as possible. AVISO: Uma versão obrigatória do cliente Gridcoin está disponível. Por favor atualize o mais rapidamente possível. + + Warning: Clock skew is between 3 and 5 minutes. Please check your clock settings. + Aviso: O desvio do relógio situa-se entre 3 e 5 minutos. Verifique as definições do seu relógio. + + + Warning: ETTS is > 90 days. It will take a very long time to receive your research rewards by staking - increase balance or use MRC + Aviso: O ETTS é > 90 dias. Vai demorar muito tempo a receber as suas recompensas de investigação por staking - aumente o saldo ou utilize o MRC + + + Warning: ETTS is infinite. No coins to stake - increase balance or use MRC + Aviso: O ETTS é infinito. Não há moedas para realizar stake - aumente o saldo ou use o MRC + Warning: Ending this process after Stage 2 will result in syncing from 0 or an incomplete/corrupted blockchain. - Aviso: Acabar este processo antes da Fase 2 irão resultar numa sincronização do "0", ou num blockchain incompleto/corrupto. + Aviso: Acabar este processo antes da Fase 2 irá resultar numa sincronização do "0", ou numa cadeia de blocos incompleta/corrupta. + + + You have no balance and will be unable to retrieve your research rewards when solo crunching by staking. You can use MRC to retrieve your rewards, or you should acquire GRC to stake so you can retrieve your research rewards. Please see https://gridcoin.us/guides/boinc-install.htm. + Não tens saldo e não será possível recuperar as tuas recompensas de investigação quando estiveres a fazer crunch a solo por staking. Podes usar MRC para recuperar as tuas recompensas, ou deverás adquirir GRC para fazer stake e poderes recuperar as tuas recompensas de investigação. Consulte https://gridcoin.us/guides/boinc-install.htm. + + + You will not be able to stake because you have less than %1 connection(s). Please check your network and also check the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + Não será possível realizar stake porque tem menos de %1 ligação(ões). Por favor, verifique a sua rede e verifique também o ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode querer esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your balance is low given the current network difficulty to stake in a reasonable period of time to retrieve your research rewards when solo crunching. You should consider acquiring more GRC to stake more often, or else use MRC to retrieve your rewards. + O seu saldo é baixo dada a dificuldade atual da rede em realizar stake num período de tempo razoável para recuperar as suas recompensas de investigação quando faz crunch a solo. Deve considerar a aquisição de mais GRC para realizar stake mais vezes, ou então usar MRC para recuperar as suas recompensas. + + + Your balance is too low given the current network difficulty to stake in a reasonable period of time to retrieve your research rewards when solo crunching. You can use MRC to retrieve your rewards, or you should acquire more GRC to stake more often. + O teu saldo é demasiado baixo tendo em conta a dificuldade atual da rede, para poderes realizar stake num período de tempo razoável para recuperares as tuas recompensas de investigação quando fazes crunch a solo. Podes usar MRC para recuperar as tuas recompensas, ou deverás adquirir mais GRC para realizar stake mais vezes. + + + Your clock in your computer is significantly off from UTC or network time and this may seriously degrade the operation of the wallet, including maintaining connection to the network. You should check your time and time zone settings for your computer. A very common problem is the off by one hour caused by a time zone issue or problems with daylight savings time. + O relógio do seu computador está significativamente desfasado da hora UTC ou da hora da rede, o que pode degradar seriamente o funcionamento da carteira, incluindo a manutenção da ligação à rede. Deve verificar as definições da hora e do fuso horário do seu computador. Um problema muito comum é o desfasamento de uma hora causado por um problema de fuso horário ou por problemas com a hora de Verão. + + + Your difficulty is extremely low and your wallet is almost certainly forked. Please ensure you are running the latest version and try removing the blockchain database and resyncing from genesis using the menu option. (Note this will take 2-4 hours.) + A sua dificuldade é extremamente baixa e a sua carteira está quase de certeza bifurcada. Por favor, certifique-se de que está a correr a versão mais recente e tente remover a base de dados da cadeia de blocos e voltar a sincronizar a partir do génese usando a opção de menu. (Note que isso levará de 2-4 horas). + + + Your difficulty is low but your wallet is still in initial sync. Please recheck it later to see if this passes. + A sua dificuldade é baixa, mas a sua carteira ainda está na sincronização inicial. Volte a verificar mais tarde para ver se isto ficou resolvido. + + + Your difficulty is very low and your wallet is probably forked. Please ensure you are running the latest version and try removing the blockchain database and resyncing from genesis using the menu option. (Note this will take 2-4 hours.) + A sua dificuldade é muito baixa e a sua carteira está provavelmente bifurcada. Por favor, certifique-se de que está a correr a versão mais recente e tente remover a base de dados da cadeia de blocos e volte a sincronizar a partir da génese usando a opção de menu. (Observe que isso levará de 2-4 horas). + + + Your outbound connection count is critically low. Please check your the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + A sua contagem de ligações de saída é criticamente baixa. Por favor, verifique o seu ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode ter de esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your outbound connection count is low. Please check your the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + A sua contagem de ligações de saída é baixa. Por favor, verifique o seu ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode ter de esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your wallet is not in sync and has not previously been in sync during this run, please wait for the wallet to sync and retest. If there are other failures preventing the wallet from syncing, please correct those items and retest to see if this test passes. + A sua carteira não está sincronizada e não esteve anteriormente sincronizada durante esta execução, aguarde que a carteira sincronize e volte a testar. Se existirem outras falhas que impeçam a sincronização da carteira, corrija esses itens e volte a testar para ver se este teste é concluído com sucesso. + + + Your wallet is out of sync with the network but was in sync before. If this fails there is likely a severe problem that is preventing the wallet from syncing. If the lack of sync is due to network connection issues, you will see failures on the network connection test(s). If the network connections pass, but your wallet fails this test, and continues to fail this test on repeated attempts with a few minutes in between, this could indicate a more serious issue. In that case you should check the debug log to see if it sheds light on the cause for no sync. + A sua carteira está fora de sincronia com a rede, mas estava sincronizada anteriormente. Se isto falhar, é provável que exista um problema grave que esteja a impedir a sincronização da carteira. Se a falta de sincronização se dever a problemas de ligação à rede, verá falhas no(s) teste(s) de ligação à rede. Se as ligações de rede passarem, mas a sua carteira falhar este teste, e continuar a falhar este teste em tentativas repetidas com alguns minutos de intervalo, isto pode indicar um problema mais sério. Nesse caso, deve verificar o registo de depuração para ver se este esclarece a causa da falha na sincronização. + + + Your wallet is still in initial sync. If this is a sync from the beginning (genesis), the sync process can take from 2 to 4 hours, or longer on a slow computer. If you have synced your wallet before but you just started the wallet up, then wait a few more minutes and retry the diagnostics again. + A sua carteira ainda está na sincronização inicial. Se se tratar de uma sincronização desde o início (génese), o processo de sincronização pode demorar de 2 a 4 horas, ou mais num computador lento. Se já sincronizou a sua carteira antes, mas acabou de a iniciar, aguarde mais alguns minutos e tente executar novamente o diagnóstico. A poll choice cannot be empty. - A escolha duma votação não pode estar vazia. + A escolha duma sondagem não pode estar vazia. Are you sure you want to cancel the snapshot operation? @@ -4138,7 +4499,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. CPID count polls are not supported. - Contagem de votações CPID não são suportadas. + Contagem de sondagens CPID não são suportadas. Cancel snapshot operation? @@ -4166,15 +4527,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Duplicate poll additional field: %s - Duplicar campo adicional na votação: %s + Duplicar campo adicional na sondagem: %s Duplicate poll choice: %s - Duplicar escolhas da votação: %s + Duplicar escolhas da sondagem: %s Duplicate response for poll choice: %s - Duplicar respostas para escolha da votação %s + Duplicar respostas para escolha da sondagem %s Entire balance reserved @@ -4190,7 +4551,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Exceeded the number of choices in the poll: %s - Excedidos número de escolhas na votação: %s + Excedidos número de escolhas na sondagem: %s Failed to download snapshot.zip; See debug.log @@ -4200,6 +4561,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Failed to rename bootstrap file to .old for backup purposes. Falhou a alteração de nome por motivos de backups no ficheiro bootstrap para .old. + + Failed: 80 block difficulty is less than + Falhou: a dificuldade de 80 blocos é menor que + Failed: Count = Falhou: Contagem = @@ -4214,7 +4579,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. For opinion or casual polls without any particular requirements. - Para opiniões ou votações casuais sem requisitos especiais. + Para opiniões ou sondagens casuais sem requisitos especiais. Get help for a command @@ -4262,7 +4627,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Magnitude-only polls are not supported. - Magnitude-pools apenas não são suportadas. + Sondagens de Magnitude apenas não são suportadas. Multiple Choice @@ -4314,15 +4679,27 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Participant count polls are not supported. - Contagens de participantes das votações não são suportadas. + Contagens de participantes das sondagens não são suportadas. + + + Passed: 80 block difficulty is + Sucesso: A dificuldade de 80 blocos é + + + Passed: Count = + Sucesso: Contagem = + + + Passed: ETTS = + Sucesso: ETTS = Please enter a poll discussion website URL. - Por favor insira o URL do website para a discussão da votação. + Por favor insira o URL do sítio web para a discussão da sondagem. Please enter a poll title. - Por favor insira o titulo da votação. + Por favor insira o titulo da sondagem. Please enter at least one response. @@ -4330,47 +4707,75 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Please enter at least two poll choices. - Por favor insira pelo menos duas escolhas na votação. + Por favor insira pelo menos duas escolhas na sondagem. + + + Poll additional field name "%s" exceeds %s characters. + O nome do campo adicional da sondagem "%s" excede os %s caracteres. + + + Poll cannot contain more than %s additional fields + A sondagem não pode conter mais de %s campos adicionais Poll cannot contain more than %s choices. - Votação não pode conter mais do que %s escolhas. + A sondagem não pode conter mais do que %s escolhas. Poll choice "%s" exceeds %s characters. - Escolha %s da votação excede os %s carateres. + Escolha %s da sondagem excede os %s carateres. Poll discussion URL cannot exceed %s characters. - URL de discussão da votação não pode exceder os %s carateres. + URL de discussão da sondagem não pode exceder os %s carateres. Poll duration cannot exceed %s days. - Duração da votação não pode exceder os %s dias. + Duração da sondagem não pode exceder os %s dias. Poll duration must be at least %s days. - Duração da votação tem de durar pelo menos %s dias. + Duração da sondagem tem de durar pelo menos %s dias. Poll has already finished. - Votação já terminou. + A sondagem já terminou. Poll only allows a single choice. - Votação permite apenas uma escolha única. + A sondagem permite apenas uma escolha única. Poll question cannot exceed %s characters. - Questão da votação não pode exceder %s carateres. + Questão da sondagem não pode exceder %s carateres. Poll signature failed. See debug.log. - Assinatura da votação falhou. Veja debug.log + Assinatura da sondagem falhou. Ver debug.log Poll title cannot exceed %s characters. - Título da votação não pode exceder %s carateres. + Título da sondagem não pode exceder %s carateres. + + + Poll with that title already exists. Please choose another title. + Já existe uma sondagem com esse título. Por favor, escolha outro título. + + + Project Listing + Listagem de Projetos + + + Propose a change to Gridcoin at the protocol level. + Propor uma alteração ao Gridcon ao nível do protocolo. + + + Propose marketing initiatives like ad campaigns. + Propor iniciativas de marketing como campanhas publicitárias. + + + Protocol Development + Protocolo de Desenvolvimento Quorum Hash @@ -4378,7 +4783,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reindexing blockchain from on disk block data files... - Reindexando blockchain dos ficheiros de dados no disco... + Reindexando cadeia de blocos dos ficheiros de dados de blocos no disco... Replaying contracts... @@ -4386,11 +4791,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reset Blockchain Data: Blockchain data removal failed. - Repor Dados da Blockchain: Remoção de dados da Blockchain falhou. + Repor os Dados da Cadeia de Blocos: Remoção de dados da cadeia de blocos falhou. Reset Blockchain Data: Blockchain data removal was a success - Repor Dados da Blockchain: Remoção de dados da Blockchain realizada com sucesso + Repor os Dados da Cadeia de Blocos: Remoção de dados da cadeia de blocos realizada com sucesso Send command to -server or gridcoinresearchd @@ -4422,12 +4827,24 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Stage (3/4): Cleanup blockchain data - Etapa (3/4): Limpando dados do blockchain + Etapa (3/4): Limpando dados da cadeia de blocos Stage (4/4): Extracting snapshot.zip Etapa (4/4): Extraindo o snapshot.zip + + Survey + Questionário + + + The field is not well-formed. + O campo não está bem formado. + + + The field list is not well-formed. + A lista do campo não está bem formada. + The wallet is now shutting down. Please restart your wallet. A carteira está a encerrar. Por favor reinicie a sua carteira. @@ -4436,17 +4853,61 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The wallet will now shutdown. A carteira irá agora encerrar. + + This wallet is almost certainly forked. + A carteira está quase de certeza bifurcada. + + + This wallet is probably forked. + A carteira está provavelmente bifurcada. + + + Unable to create the PID file '%s': %s + Não foi possível criar o ficheiro PID "%s': %s + + + Unknown poll type. This should never happen. + Tipo de sondagem desconhecida. Isto nunca deverá acontecer. + + + Warning: 45 days < ETTS = + Aviso: 45 dias < ETTS = + + + Warning: 80 block difficulty is less than + Aviso: A dificuldade de 80 blocos é menor que + + + Warning: Cannot connect to NTP server + Aviso: Não foi possível ligar ao servidor NTP + + + Warning: Count = + Aviso: Contagem = + + + Wrong Payload version specified for current block height. + Versão de carga útil incorreta especificada para a altura do bloco atual. + Yes/No/Abstain Sim/Não/Abster-se + + You should check your time and time zone settings for your computer. + Deve verificar as definições de hora e fuso horário do seu computador. + You will need to delete the following. Irá necessitar de eliminar o seguinte. "%s" is not a valid poll choice. - %s não é uma escolha válida da votação. + "%s" não é uma escolha válida da sondagem. + + + appears to be blocked. + parece estar bloqueado. leisure @@ -4468,10 +4929,18 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The %s developers Os %s desenvolvedores + + Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported. + Erro: Argumento não suportado -socks encontrado. Definir a versão SOCKS não é mais possível, são apenas suportadas proxies SOCKS5. + Block Version Versão do Bloco + + Block file load progress + Progresso do carregamento do ficheiro de blocos + Block not in index Bloco em falha no index @@ -4534,7 +5003,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Importing blockchain data file(s). - Importando ficheiro(s) de dados da blockchain. + Importando ficheiro(s) de dados da cadeia de blocos. Interest @@ -4552,6 +5021,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Is Superblock É um Super Bloco + + Latest Version GitHub data response: + Versão mais recente da resposta de dados do GitHub: + Loading Network Averages... Carregando Médias de Rede... @@ -4638,12 +5111,16 @@ por exemplo: alertnotify=echo %%s | mail -s "Gridcoin Alert" admin@foo.com Due to the failure to delete the blockchain data you will be required to manually delete the data before starting your wallet. - Devido à falha em eliminar os dados da blockchain, irá necessitar de eliminar manualmente os dados antes de começar de iniciar a sua carteira. + Devido à falha na eliminação dos dados da cadeia de blocos, ser-lhe-á solicitado que elimine manualmente os dados antes de iniciar a sua carteira. Failed to download snapshot as mandatory client is available for download. Falhou a transferência do snapshot porque existe uma atualização obrigatória da aplicação. + + Failure to do so will result in undefined behaviour or failure to start wallet. + Se não o fizer, o resultado será um comportamento indefinido ou uma falha no arranque da carteira. + Unable to download a snapshot, as the wallet has detected that a new mandatory version is available for install. The mandatory upgrade must be installed before the snapshot can be downloaded and applied. Não foi possível transferir o snapshot, porque a carteira detetou que uma versão obrigatória mais recente, está disponível para instalar. A atualização obrigatória deve ser instalada antes que o snapshot possa ser transferido e aplicado. @@ -4659,7 +5136,7 @@ Please exit Gridcoin, open the data directory, and delete: Your wallet will re-download the blockchain. Your balance may appear incorrect until the synchronization finishes. - AVISO: A Blockchain pode estar corrompida. + AVISO: A Cadeia de Blocos pode estar corrompida. O Gridcoin detetou entradas incorretas do índice. Isto pode ocorrer devido uma fecho inesperado, falha de energia ou uma atualização da aplicação. @@ -4667,7 +5144,7 @@ Por favor saia do Gridcoin, abra a diretoria de dados e, elimine: - os ficheiros blk****.dat - a diretoria txleveldb -A sua carteira irá transferir novamente a blockchain. O seu balanço poder aparecer incorreto até que a sincronização termine. +A sua carteira irá transferir novamente a cadeia de blocos. O seu balanço poder aparecer incorreto até que a sincronização termine. @@ -4682,6 +5159,10 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Gridcoin version Versão do Gridcoin + + Resetting block chain index to prepare for reindexing... + Repondo o índice da block chain para preparar a reindexação... + Stage (1/4): Downloading snapshot.zip: Etapa (1/4): Transferindo o snapshot.zip: @@ -4692,7 +5173,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Stage (3/4): Cleanup blockchain data: - Etapa (3/4): Limpar dados da blockchain: + Etapa (3/4): Limpar dados da cadeia de blocos: Stage (4/4): Extracting snapshot.zip: @@ -4736,15 +5217,15 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Unknown poll response type. - Tipo de resposta da votação desconhecido. + Tipo de resposta da sondagem desconhecida. Unknown poll type. - Tipo de votação desconhecido. + Tipo de sondagem desconhecida. Unknown poll weight type. - Tipo de peso da votação desconhecido. + Tipo de peso da sondagem desconhecido. None @@ -4752,7 +5233,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. No current polls - Sem votações + Sem sondagens Invalid amount for -paytxfee=<amount>: '%s' @@ -4832,7 +5313,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Importing bootstrap blockchain data file. - Importando ficheiro de dados do bootstrap da blockchain. + Importação do ficheiro de dados da cadeia de blocos bootstrap. Loading addresses... @@ -4876,7 +5357,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Vote signature failed. See debug.log. - Assinatura da votação falhou. Veja debug.log. + Assinatura da votação falhou. Ver debug.log. Warning: Disk space is low! diff --git a/src/qt/locale/bitcoin_vi.ts b/src/qt/locale/bitcoin_vi.ts index c68b47437f..454b9c557f 100644 --- a/src/qt/locale/bitcoin_vi.ts +++ b/src/qt/locale/bitcoin_vi.ts @@ -9,7 +9,21 @@ <b>Gridcoin</b> Gridcoin - + + +This is experimental software. + +Distributed under the MIT/X11 software license, see the accompanying file COPYING or https://opensource.org/licenses/mit-license.php. + +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (https://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. + +Đây là phần mềm thử nghiệm. + +Được phân phối theo giấy phép phần mềm MIT/X11, xem tệp COPYING đi kèm hoặc https://opensource.org/licenses/mit-license.php. + +Sản phẩm này bao gồm phần mềm do Dự án OpenSSL phát triển để sử dụng trong Bộ công cụ OpenSSL (https://www.openssl.org/) và phần mềm mã hóa được viết bởi Eric Young (eay@cryptsoft.com) và phần mềm UPnP được viết bởi Thomas Bernard. + + AdditionalFieldsTableDataModel @@ -33,7 +47,7 @@ &New - Tạo mới + &Tạo mới Copy the currently selected address to the system clipboard @@ -41,7 +55,11 @@ &Copy - Sao chép + &Sao chép + + + Show &QR Code + Hiển thị &mã QR Address Book @@ -51,12 +69,20 @@ &Delete &Xóa + + Copy &Label + Sao chép &nhãn dữ liệu + + + &Edit + &Chỉnh sửa + AddressTableModel Label - Nhãn d? li?u + Nhãn dữ liệu Address @@ -69,53 +95,197 @@ AskPassphraseDialog + + Passphrase Dialog + Hộp thoại cụm mật khẩu + + + Enter passphrase + Nhập cụm mật khẩu + + + New passphrase + Cụm mật khẩu mới + + + Repeat new passphrase + Nhập lại cụm mật khẩu mới + + + Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>. + Nhập cụm mật khẩu mới vào ví. <br/>Vui lòng sử dụng cụm mật khẩu gồm <b>mười ký tự ngẫu nhiên trở lên</b> hoặc <b>tám từ trở lên</b>. + Encrypt wallet Mã hóa ví + + This operation needs your wallet passphrase to unlock the wallet. + Thao tác này cần có cụm mật khẩu ví của bạn để mở khóa ví. + Unlock wallet Mở khóa ví + + Change passphrase + Thay đổi cụm mật khẩu + + + Enter the old and new passphrase to the wallet. + Nhập cụm mật khẩu cũ và cụm mật khẩu mới vào ví + Confirm wallet encryption Xác nhận mã hóa ví + + Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR COINS</b>! + Cảnh báo: Nếu bạn mã hóa ví và làm mất cụm mật khẩu, bạn sẽ <b>MẤT TẤT CẢ SỐ TIỀN CỦA MÌNH</b>! + + + Are you sure you wish to encrypt your wallet? + Bạn có chắc chắn muốn mã hóa ví của mình không? + Wallet encrypted Ví đã được mã hóa + + Gridcoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your coins from being stolen by malware infecting your computer. + Gridcoin sẽ đóng để hoàn tất quá trình mã hóa. Hãy nhớ rằng việc mã hóa ví của bạn không thể bảo vệ hoàn toàn tiền của bạn khỏi việc bị đánh cắp trong trường hợp máy bạn bị lây nhiễm mã độc (virus). + Wallet encryption failed Mã hóa ví thất bại + + The supplied passphrases do not match. + Cụm mật khẩu được cung cấp không khớp. + Wallet unlock failed Mở khóa ví thất bại - + + The passphrase entered for the wallet decryption was incorrect. + Cụm mật khẩu được nhập để giải mã ví không chính xác. + + + Wallet passphrase was successfully changed. + Cụm mật khẩu ví đã được thay đổi thành công. + + + Warning: The Caps Lock key is on! + Cảnh báo: Đang bật phím Caps Lock! + + BitcoinGUI Wallet + + &Overview + &Tổng quan + + + Show general overview of wallet + Hiển thị tổng quan ví + + + &Send + &Gửi + + + Send coins to a Gridcoin address + Gửi tiền đến một địa chỉ Gridcoin + + + &Receive + &Nhận + + + Show the list of addresses for receiving payments + Hiển thị các địa chỉ dùng để nhận thanh toán + + + &History + &Lịch sử + + + &Web Site + &Trang web + + + &GRC Chat Room + &Phòng chat GRC + GRC Chatroom Phòng chat GRC + + E&xit + T&hoát + Quit application Thoát ứng dụng + + &About Gridcoin + &Về Gridcoin + + + Show information about Gridcoin + Hiển thị thông tin về Gridcoin + + + &Options... + &Tùy chọn... + + + &Show / Hide + &Hiển thị / Ẩn + + + &Encrypt Wallet... + &Mã hóa ví... + Encrypt wallet Mã hóa ví + + &Change Passphrase... + &Thay đổi cụm mật khẩu... + + + &Unlock Wallet... + &Mở khóa ví... + + + &Lock Wallet + &Khóa ví + Lock wallet Khóa ví + + &Debug window + &Cửa sổ gỡ lỗi + + + &File + &Tệp + + + &Community + &Cộng đồng + %n active connection(s) to the Gridcoin network @@ -152,6 +322,10 @@ + + Warning: After the blockchain data is deleted, the wallet will shutdown and when restarted will begin syncing from zero. Your balance will temporarily show as 0 GRC while syncing. + Cảnh báo: Sau khi dữ liệu blockchain của bạn bị xóa, ví sẽ tắt và khi khởi động lại ví sẽ bắt đầu đồng bộ hóa lại từ đầu. Số dư tài khoản của bạn sẽ tạm thời hiển thị là 0 GRC trong lúc thực hiện đồng bộ hóa. + Close Confirmation Đóng xác nhận @@ -174,7 +348,7 @@ day - ngà + ngày hour @@ -206,10 +380,18 @@ Select All Chọn tất cả + + Tree &mode + &Chế độ cây + Amount Số lượng + + &List mode + &Chế độ danh sách + Address Địa chỉ @@ -220,7 +402,14 @@ (no label) - (ch?a có nhãn) + (chưa có nhãn) + + + + ConsolidateUnspentWizardSelectInputsPage + + Fee + Phí @@ -245,11 +434,11 @@ EditAddressDialog &Label - Nhãn dữ liệu + &Nhãn dữ liệu &Address - Địa chỉ + &Địa chỉ @@ -273,6 +462,32 @@ + + OptionsDialog + + &Port: + &Cổng: + + + &Window + &Cửa sổ + + + User Interface &language: + &Ngôn ngữ giao diện người dùng: + + + &Cancel + &Hủy + + + + OverviewPage + + Wallet + + + QObject @@ -312,15 +527,93 @@ + + QRCodeDialog + + QR Code Dialog + Hộp thoại mã QR + + + &Save As... + &Lưu dưới dạng... + + + Error encoding URI into QR Code. + Lỗi mã hóa URI thành mã QR + + + Save QR Code + Lưu mã QR + + + + RPCConsole + + &Information + &Thông tin + + + &Open + &Mở + + + 1 &hour + 1 &giờ + + + 1 &day + 1 &ngày + + + 1 &week + 1 &tuần + + + 1 &year + 1 &năm + + + + ResearcherWizard + + &Start Over + &Bắt đầu lại + + + + ResearcherWizardAuthPage + + &Copy + &Sao chép + + + + ResearcherWizardPoolPage + + &Copy + &Sao chép + + + + ResearcherWizardSummaryPage + + &Projects + &Dự án + + SendCoinsDialog Amount: Số lượng: + + S&end + G&ửi + (no label) - (ch?a có nhãn) + (chưa có nhãn) @@ -345,18 +638,18 @@ Amount - S? l??ng + Số lượng TransactionTableModel Address - ??a ch? + Địa chỉ Amount - S? l??ng + Số lượng Open for %n more block(s) @@ -369,15 +662,65 @@ TransactionView Label - Nhãn d? li?u + Nhãn dữ liệu Address - ??a ch? + Địa chỉ Amount - S? l??ng + Số lượng + + UpgradeQt + + &File + &Tệp + + + + bitcoin-core + + Invalid amount + Số tiền không hợp lệ + + + Warning: Please check that your computer's date and time are correct! If your clock is wrong Gridcoin will not work properly. + Cảnh báo: Vui lòng kiểm tra xem ngày và giờ trên máy tính của bạn có chính xác không! Nếu đồng hồ của bạn sai, Gridcoin sẽ không hoạt động bình thường. + + + Warning: Disk space is low! + Cảnh báo: Dung lượng ổ đĩa thấp! + + + Insufficient funds + Nguồn tiền không đủ + + + Loading block index... + Đang tải chỉ mục khối (block index)... + + + Loading wallet... + Đang tải ví... + + + Cannot write default address + Không thể ghi địa chỉ mặc định + + + Rescanning... + Đang quét lại... + + + Done loading + Đã tải xong + + + Error + Lỗi + + \ No newline at end of file diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 2ecf594a3b..405ed34b62 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -255,8 +255,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) // This is similar to createmrcrequest in many ways, but the state tracking is more complicated. - LOCK(cs_main); - // Record initial block height during init run. if (!m_init_block_height) { m_init_block_height = pindexBest->nHeight; diff --git a/src/qt/sidestaketablemodel.cpp b/src/qt/sidestaketablemodel.cpp index 140307591a..018cb0977e 100644 --- a/src/qt/sidestaketablemodel.cpp +++ b/src/qt/sidestaketablemodel.cpp @@ -238,9 +238,12 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu prior_total_allocation += allocation; } - GRC::Allocation modified_allocation(value.toDouble() / 100.0); + bool parse_ok = false; + double read_allocation = value.toDouble(&parse_ok) / 100.0; - if (modified_allocation < 0 || prior_total_allocation + modified_allocation > 1) { + GRC::Allocation modified_allocation(read_allocation); + + if (!parse_ok || modified_allocation < 0 || prior_total_allocation + modified_allocation > 1) { m_edit_status = INVALID_ALLOCATION; LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: m_edit_status = %i", @@ -359,7 +362,6 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc } GRC::Allocation prior_total_allocation; - GRC::Allocation sidestake_allocation; // Get total allocation of all active/mandatory sidestake entries for (const auto& entry : registry.ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, true)) { @@ -367,21 +369,20 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc } // The new allocation must be parseable as a double, must be greater than or equal to 0, and - // must result in a total allocation of less than 100%. - double read_allocation = 0.0; + // must result in a total allocation of less than or equal to 100%. + bool parse_ok = false; + double read_allocation = allocation.toDouble(&parse_ok) / 100.0; - if (!ParseDouble(allocation.toStdString(), &read_allocation)) { - if (read_allocation < 0.0) { - m_edit_status = INVALID_ALLOCATION; - return QString(); - } + GRC::Allocation sidestake_allocation(read_allocation); - sidestake_allocation += GRC::Allocation(read_allocation / 100.0); + if (!parse_ok || sidestake_allocation < 0 || prior_total_allocation + sidestake_allocation > 1) { + m_edit_status = INVALID_ALLOCATION; - if (prior_total_allocation + sidestake_allocation > 1) { - m_edit_status = INVALID_ALLOCATION; - return QString(); - } + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: m_edit_status = %i", + __func__, + (int) m_edit_status); + + return QString(); } std::string sidestake_description = description.toStdString(); diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 7a58dff1ee..48b26bdbaf 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -216,7 +216,7 @@ class TransactionTablePriv // // If a status update is needed (blocks came in since last check), // update the status of this transaction from the wallet. Otherwise, - // simply re-use the cached status. + // simply reuse the cached status. TRY_LOCK(cs_main, lockMain); if(lockMain) { diff --git a/src/qt/updatedialog.cpp b/src/qt/updatedialog.cpp new file mode 100644 index 0000000000..c5e55649d5 --- /dev/null +++ b/src/qt/updatedialog.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "updatedialog.h" +#include "qicon.h" +#include "qstyle.h" +#include "qt/decoration.h" + +#include "ui_updatedialog.h" + +UpdateDialog::UpdateDialog(QWidget* parent) + : QDialog(parent) + , ui(new Ui::UpdateDialog) +{ + ui->setupUi(this); + + resize(GRC::ScaleSize(this, width(), height())); +} + +UpdateDialog::~UpdateDialog() +{ + delete ui; +} + +void UpdateDialog::setVersion(QString version) +{ + ui->versionData->setText(version); +} + +void UpdateDialog::setDetails(QString message) +{ + ui->versionDetails->setText(message); +} + +void UpdateDialog::setUpgradeType(GRC::Upgrade::UpgradeType upgrade_type) +{ + QIcon info_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + QIcon warning_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); + QIcon unknown_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); + + switch (upgrade_type) { + case GRC::Upgrade::UpgradeType::Mandatory: + [[fallthrough]]; + case GRC::Upgrade::UpgradeType::Unsupported: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, warning_icon, 48)); + break; + case GRC::Upgrade::UpgradeType::Leisure: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, info_icon, 48)); + break; + case GRC::Upgrade::Unknown: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, unknown_icon, 48)); + break; + } +} diff --git a/src/qt/updatedialog.h b/src/qt/updatedialog.h new file mode 100644 index 0000000000..7987b706f7 --- /dev/null +++ b/src/qt/updatedialog.h @@ -0,0 +1,32 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_UPDATEDIALOG_H +#define BITCOIN_QT_UPDATEDIALOG_H + +#include "gridcoin/upgrade.h" +#include + +namespace Ui { +class UpdateDialog; +} + +class UpdateDialog : public QDialog +{ + Q_OBJECT + +public: + explicit UpdateDialog(QWidget* parent = nullptr); + ~UpdateDialog(); + + void setVersion(QString version); + void setDetails(QString message); + void setUpgradeType(GRC::Upgrade::UpgradeType upgrade_type); + +private: + Ui::UpdateDialog *ui; + +}; + +#endif // BITCOIN_QT_UPDATEDIALOG_H diff --git a/src/qt/upgradeqt.cpp b/src/qt/upgradeqt.cpp index 6bbfd9519d..9499d5a032 100644 --- a/src/qt/upgradeqt.cpp +++ b/src/qt/upgradeqt.cpp @@ -41,8 +41,10 @@ bool UpgradeQt::SnapshotMain(QApplication& SnapshotApp) // Verify a mandatory release is not available before we continue to snapshot download. std::string VersionResponse = ""; + std::string change_log; + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - if (UpgradeMain.CheckForLatestUpdate(VersionResponse, false, true)) + if (UpgradeMain.CheckForLatestUpdate(VersionResponse, change_log, upgrade_type, false, true)) { ErrorMsg(UpgradeMain.ResetBlockchainMessages(Upgrade::UpdateAvailable), UpgradeMain.ResetBlockchainMessages(Upgrade::GithubResponse) + "\r\n" + VersionResponse); diff --git a/src/qt/voting/additionalfieldstablemodel.cpp b/src/qt/voting/additionalfieldstablemodel.cpp index a8f8148fa6..85f99cb639 100644 --- a/src/qt/voting/additionalfieldstablemodel.cpp +++ b/src/qt/voting/additionalfieldstablemodel.cpp @@ -177,7 +177,7 @@ void AdditionalFieldsTableModel::refresh() ->reload(additional_fields); } -Qt::SortOrder AdditionalFieldsTableModel::sort(int column) +Qt::SortOrder AdditionalFieldsTableModel::custom_sort(int column) { if (sortColumn() == column) { QSortFilterProxyModel::sort(column, static_cast(!sortOrder())); diff --git a/src/qt/voting/additionalfieldstablemodel.h b/src/qt/voting/additionalfieldstablemodel.h index 596db7ef53..40cd3703dd 100644 --- a/src/qt/voting/additionalfieldstablemodel.h +++ b/src/qt/voting/additionalfieldstablemodel.h @@ -42,7 +42,7 @@ class AdditionalFieldsTableModel : public QSortFilterProxyModel public slots: void refresh(); - Qt::SortOrder sort(int column); + Qt::SortOrder custom_sort(int column); private: const PollItem* m_poll_item; diff --git a/src/qt/voting/polltab.cpp b/src/qt/voting/polltab.cpp index 672ab759ac..46f09e9708 100644 --- a/src/qt/voting/polltab.cpp +++ b/src/qt/voting/polltab.cpp @@ -205,7 +205,7 @@ void PollTab::filter(const QString& needle) void PollTab::sort(const int column) { - const Qt::SortOrder order = m_polltable_model->sort(column); + const Qt::SortOrder order = m_polltable_model->custom_sort(column); ui->table->horizontalHeader()->setSortIndicator(column, order); } diff --git a/src/qt/voting/polltablemodel.cpp b/src/qt/voting/polltablemodel.cpp index 8f4a467c8e..880dcf71f1 100644 --- a/src/qt/voting/polltablemodel.cpp +++ b/src/qt/voting/polltablemodel.cpp @@ -15,7 +15,6 @@ using namespace GRC; -namespace { PollTableDataModel::PollTableDataModel() { qRegisterMetaType>(); @@ -189,7 +188,6 @@ void PollTableDataModel::handlePollStaleFlag(QString poll_txid_string) emit layoutChanged(); } -} // Anonymous namespace // ----------------------------------------------------------------------------- // Class: PollTableModel @@ -294,7 +292,7 @@ void PollTableModel::changeTitleFilter(const QString& pattern) emit layoutChanged(); } -Qt::SortOrder PollTableModel::sort(int column) +Qt::SortOrder PollTableModel::custom_sort(int column) { if (sortColumn() == column) { QSortFilterProxyModel::sort(column, static_cast(!sortOrder())); diff --git a/src/qt/voting/polltablemodel.h b/src/qt/voting/polltablemodel.h index 5f3b1ac406..eecf65f5f4 100644 --- a/src/qt/voting/polltablemodel.h +++ b/src/qt/voting/polltablemodel.h @@ -15,7 +15,6 @@ class PollItem; class VotingModel; -namespace { class PollTableDataModel : public QAbstractTableModel { public: @@ -35,7 +34,6 @@ class PollTableDataModel : public QAbstractTableModel std::vector m_rows; }; -} // Anonymous namespace class PollTableModel : public QSortFilterProxyModel { @@ -80,7 +78,7 @@ class PollTableModel : public QSortFilterProxyModel public slots: void refresh(); void changeTitleFilter(const QString& pattern); - Qt::SortOrder sort(int column); + Qt::SortOrder custom_sort(int column); void handlePollStaleFlag(QString poll_txid_string); diff --git a/src/qt/voting/pollwizarddetailspage.cpp b/src/qt/voting/pollwizarddetailspage.cpp index 140e66835e..1e75926e76 100644 --- a/src/qt/voting/pollwizarddetailspage.cpp +++ b/src/qt/voting/pollwizarddetailspage.cpp @@ -3,6 +3,7 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "main.h" +#include "qitemdelegate.h" #include "qt/bitcoinunits.h" #include "qt/decoration.h" #include "qt/forms/voting/ui_pollwizarddetailspage.h" @@ -54,6 +55,37 @@ class ChoicesListDelegate : public QStyledItemDelegate } }; // ChoicesListDelegate +//! +//! \brief Applies custom behavior to additional field items in the poll editor. +//! +class AdditionalFieldDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + AdditionalFieldDelegate(QObject* parent = nullptr) : QItemDelegate(parent) + { + } + + QWidget* createEditor( + QWidget* parent, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + QWidget* editor = QItemDelegate::createEditor(parent, option, index); + + if (QLineEdit* line_edit = qobject_cast(editor)) { + if (index.column() == AdditionalFieldsTableModel::Name) { + line_edit->setMaxLength(VotingModel::maxPollAdditionalFieldNameLength()); + } else if (index.column() == AdditionalFieldsTableModel::Value) { + line_edit->setMaxLength(VotingModel::maxPollAdditionalFieldValueLength()); + } + } + + return editor; + } +}; // AdditionalFieldDelegate + //! //! \brief Provides for QWizardPage::registerField() without a real widget. //! @@ -167,6 +199,7 @@ PollWizardDetailsPage::PollWizardDetailsPage(QWidget* parent) ui->responseTypeList->addItem(tr("Multiple Choice")); ChoicesListDelegate* choices_delegate = new ChoicesListDelegate(this); + AdditionalFieldDelegate* additonal_field_delegate = new AdditionalFieldDelegate(this); ui->choicesList->setModel(m_choices_model.get()); ui->choicesList->setItemDelegate(choices_delegate); @@ -174,6 +207,12 @@ PollWizardDetailsPage::PollWizardDetailsPage(QWidget* parent) ui->editChoiceButton->hide(); ui->removeChoiceButton->hide(); + ui->additionalFieldsTableView->setItemDelegate(additonal_field_delegate); + + ui->titleField->setMaxLength(m_voting_model->maxPollTitleLength()); + ui->questionField->setMaxLength(m_voting_model->maxPollQuestionLength()); + ui->urlField->setMaxLength(m_voting_model->maxPollUrlLength()); + connect( ui->responseTypeList, QOverload::of(&QComboBox::currentIndexChanged), this, [=](int index) { @@ -261,12 +300,7 @@ void PollWizardDetailsPage::initializePage() // Only populate poll additional field entries if version >= 3. bool v3_enabled = false; - - { - AssertLockHeld(cs_main); - - v3_enabled = IsPollV3Enabled(nBestHeight); - } + v3_enabled = IsPollV3Enabled(nBestHeight); if (v3_enabled) { poll_item.m_additional_field_entries.push_back( diff --git a/src/qt/voting/pollwizardprojectpage.cpp b/src/qt/voting/pollwizardprojectpage.cpp index f825155be5..1b509d3218 100644 --- a/src/qt/voting/pollwizardprojectpage.cpp +++ b/src/qt/voting/pollwizardprojectpage.cpp @@ -31,6 +31,9 @@ PollWizardProjectPage::PollWizardProjectPage(QWidget* parent) ui->removeWidget->hide(); ui->addRemoveStateLineEdit->hide(); + ui->projectNameField->setMaxLength(m_voting_model->maxPollProjectNameLength()); + ui->projectUrlField->setMaxLength(m_voting_model->maxPollProjectUrlLength()); + QStringListModel* project_names_model = new QStringListModel(this); QStringListModel* project_urls_model = new QStringListModel(this); diff --git a/src/qt/voting/votingmodel.cpp b/src/qt/voting/votingmodel.cpp index 2efed942a7..dc55983262 100644 --- a/src/qt/voting/votingmodel.cpp +++ b/src/qt/voting/votingmodel.cpp @@ -217,6 +217,42 @@ int VotingModel::maxPollChoiceLabelLength() return Poll::Choice::MAX_LABEL_SIZE; } +int VotingModel::maxPollAdditionalFieldNameLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Poll::AdditionalField::MAX_NAME_SIZE; +} + +int VotingModel::maxPollAdditionalFieldValueLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Poll::AdditionalField::MAX_VALUE_SIZE; +} + +int VotingModel::maxPollProjectNameLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Project::MAX_NAME_SIZE; +} + +int VotingModel::maxPollProjectUrlLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Project::MAX_URL_SIZE; +} + OptionsModel& VotingModel::getOptionsModel() { return m_options_model; diff --git a/src/qt/voting/votingmodel.h b/src/qt/voting/votingmodel.h index f207948ecc..435eef240a 100644 --- a/src/qt/voting/votingmodel.h +++ b/src/qt/voting/votingmodel.h @@ -130,6 +130,10 @@ class VotingModel : public QObject static int maxPollUrlLength(); static int maxPollQuestionLength(); static int maxPollChoiceLabelLength(); + static int maxPollAdditionalFieldNameLength(); + static int maxPollAdditionalFieldValueLength(); + static int maxPollProjectNameLength(); + static int maxPollProjectUrlLength(); OptionsModel& getOptionsModel(); QString getCurrentPollTitle() const; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 7cb909d00b..6c25237a13 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -107,7 +107,10 @@ class WalletModel : public QObject // Copy operator and constructor transfer the context UnlockContext(const UnlockContext& obj) { CopyFrom(obj); } - UnlockContext& operator=(const UnlockContext& rhs) { CopyFrom(rhs); return *this; } + + // Commented out as we don't use the below form and it triggers an infinite recursion + // warning. + // UnlockContext& operator=(const UnlockContext& rhs) { CopyFrom(rhs); return *this; } private: WalletModel *wallet; bool valid; diff --git a/src/random.h b/src/random.h index 821927af92..066ad1e68f 100644 --- a/src/random.h +++ b/src/random.h @@ -100,7 +100,7 @@ constexpr auto GetRandMillis = GetRandomDuration; * is memoryless and should be used for repeated network events (e.g. sending a * certain type of message) to minimize leaking information to observers. * - * The probability of an event occuring before time x is 1 - e^-(x/a) where a + * The probability of an event occurring before time x is 1 - e^-(x/a) where a * is the average interval between events. * */ std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 5adab057b5..8c3964a9a4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1750,7 +1750,7 @@ UniValue beaconstatus(const UniValue& params, bool fHelp) active.push_back(entry); } - for (auto beacon_ptr : beacons.FindPending(*cpid)) { + for (const auto& beacon_ptr : beacons.FindPending(*cpid)) { UniValue entry(UniValue::VOBJ); entry.pushKV("cpid", cpid->ToString()); entry.pushKV("active", false); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b1a76da8f8..9724c04aa9 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -352,8 +352,8 @@ int CommandLineRPC(int argc, char *argv[]) const UniValue reply = CallRPC(strMethod, params); // Parse reply - const UniValue& result = find_value(reply, "result"); - const UniValue& error = find_value(reply, "error"); + UniValue result = find_value(reply, "result"); + UniValue error = find_value(reply, "error"); if (!error.isNull()) { diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 69eb2eaae9..be24b0b100 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -300,47 +300,29 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) GRC::Beacon_ptr beacon_ptr = beacon_try; - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: active beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s", - __func__, - beacon_ptr->m_timestamp, - beacon_ptr->m_hash.GetHex(), - beacon_ptr->m_previous_hash.GetHex()); + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); UniValue beacon_chain(UniValue::VARR); - UniValue beacon_chain_entry(UniValue::VOBJ); - - beacon_chain_entry.pushKV("ctx_hash", beacon_ptr->m_hash.GetHex()); - beacon_chain_entry.pushKV("timestamp", beacon_ptr->m_timestamp); - beacon_chain.push_back(beacon_chain_entry); - - // This walks back the entries in the historical beacon map linked by renewal prev tx hash until the first - // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier than here. - uint64_t renewals = 0; - // The renewals <= 100 is simply to prevent an infinite loop if there is a problem with the beacon chain in the registry. This - // was an issue in post Fern beacon db work, but has been resolved and not encountered since. Still makes sense to leave the - // limit in, which represents 41 years worth of beacon chain at the 150 day standard auto-renewal cycle. - while (beacon_ptr->Renewed() && renewals <= 100) - { - auto iter = beacons.GetBeaconDB().find(beacon_ptr->m_previous_hash); - beacon_ptr = iter->second; + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon_ptr, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + throw JSONRPCError(RPC_INTERNAL_ERROR, e.what()); + } - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: renewal %u beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s.", - __func__, - renewals, - beacon_ptr->m_timestamp, - beacon_ptr->m_hash.GetHex(), - beacon_ptr->m_previous_hash.GetHex()); + for (const auto& iter : *beacon_chain_out_ptr) { + UniValue beacon_chain_entry(UniValue::VOBJ); - beacon_chain_entry.pushKV("ctx_hash", beacon_ptr->m_hash.GetHex()); - beacon_chain_entry.pushKV("timestamp", beacon_ptr->m_timestamp); + beacon_chain_entry.pushKV("ctx_hash", iter.first.GetHex()); + beacon_chain_entry.pushKV("timestamp", iter.second); beacon_chain.push_back(beacon_chain_entry); - - ++renewals; } + int64_t renewals = beacon_chain_out_ptr->size() - 1; + bool retry_from_baseline = false; // Up to two passes. The first is from the start of the current beacon chain for the CPID, the second from the Fern baseline. diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index d3688e459b..ef570f9da7 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1528,14 +1528,14 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); - const UniValue& txid_v = find_value(o, "txid"); + UniValue txid_v = find_value(o, "txid"); if (!txid_v.isStr()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing txid key"); string txid = txid_v.get_str(); if (!IsHex(txid)) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); - const UniValue& vout_v = find_value(o, "vout"); + UniValue vout_v = find_value(o, "vout"); if (!vout_v.isNum()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); int nOutput = vout_v.get_int(); diff --git a/src/script.cpp b/src/script.cpp index 45d5e9000f..e81af2bfcf 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1102,7 +1102,7 @@ class CSignatureCache { // Evict a random entry. Random because that helps // foil would-be DoS attackers who might try to pre-generate - // and re-use a set of valid signatures just-slightly-greater + // and reuse a set of valid signatures just-slightly-greater // than our cache size. uint256 randomHash = GetRandHash(); std::vector unused; diff --git a/src/test/dos_tests.cpp b/src/test/dos_tests.cpp index ff7ec22e75..82653c0632 100755 --- a/src/test/dos_tests.cpp +++ b/src/test/dos_tests.cpp @@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vin[j].prevout.hash = txPrev.GetHash(); } BOOST_CHECK(SignSignature(keystore, txPrev, tx, 0)); - // Re-use same signature for other inputs + // Reuse same signature for other inputs // (they don't have to be valid for this test) for (unsigned int j = 1; j < tx.vin.size(); j++) tx.vin[j].scriptSig = tx.vin[0].scriptSig; diff --git a/src/test/gridcoin/beacon_tests.cpp b/src/test/gridcoin/beacon_tests.cpp index 39b4feefbc..e216919f3a 100644 --- a/src/test/gridcoin/beacon_tests.cpp +++ b/src/test/gridcoin/beacon_tests.cpp @@ -86,6 +86,11 @@ struct TestKey return EncodeBase58(key_id.begin(), key_id.end()); } + static GRC::Cpid Cpid() + { + return GRC::Cpid::Parse("00010203040506070809101112131415"); + } + //! //! \brief Create a beacon payload signature signed by this private key. //! @@ -96,7 +101,7 @@ struct TestKey hasher << GRC::BeaconPayload::CURRENT_VERSION << GRC::Beacon(Public()) - << GRC::Cpid::Parse("00010203040506070809101112131415"); + << Cpid(); std::vector signature; CKey private_key = Private(); @@ -105,6 +110,27 @@ struct TestKey return signature; } + + //! + //! \brief Create a beacon payload signature signed by this private key. + //! + static std::vector Signature(GRC::BeaconPayload payload) + { + CHashWriter hasher(SER_NETWORK, PROTOCOL_VERSION); + + hasher + << payload.m_version + << payload.m_beacon + << payload.m_cpid; + + std::vector signature; + CKey private_key = Private(); + + private_key.Sign(hasher.GetHash(), signature); + + return signature; + } + }; // TestKey @@ -176,21 +202,96 @@ class BeaconRegistryTest if (ctx->m_action == GRC::ContractAction::ADD) { registry.Add(ctx); + + GRC::Beacon_ptr beacon = registry.FindHistorical(ctx.m_tx.GetHash()); + + if (beacon != nullptr) { + std::cout << "add beacon record: " + << "blockheight = " << ctx.m_pindex->nHeight + << ", cpid = " << beacon->m_cpid.ToString() + << ", public key = " << HexStr(beacon->m_public_key) + << ", address = " << beacon->GetAddress().ToString() + << ", timestamp = " << beacon->m_timestamp + << ", hash = " << beacon->m_hash.GetHex() + << ", prev beacon hash = " << beacon->m_previous_hash.GetHex() + << ", status = " << beacon->StatusToString() + << std::endl; + } } if (ctx->m_action == GRC::ContractAction::REMOVE) { registry.Delete(ctx); + + GRC::Beacon_ptr beacon = registry.FindHistorical(ctx.m_tx.GetHash()); + + if (beacon != nullptr) { + std::cout << "delete beacon record: " + << "blockheight = " << ctx.m_pindex->nHeight + << ", cpid = " << beacon->m_cpid.ToString() + << ", public key = " << HexStr(beacon->m_public_key) + << ", address = " << beacon->GetAddress().ToString() + << ", timestamp = " << beacon->m_timestamp + << ", hash = " << beacon->m_hash.GetHex() + << ", prev beacon hash = " << beacon->m_previous_hash.GetHex() + << ", status = " << beacon->StatusToString() + << std::endl; + } } } // Activate the pending beacons that are now verified, and also mark expired pending beacons expired. if (pindex->IsSuperblock()) { + std::vector pending_beacon_hashes; + + for (const auto& iter : element.m_verified_beacons) { + auto found_beacon_iter = registry.PendingBeacons().find(iter); + + if (found_beacon_iter != registry.PendingBeacons().end()) { + pending_beacon_hashes.push_back(found_beacon_iter->second->m_hash); + } + } + registry.ActivatePending(element.m_verified_beacons, pindex->nTime, block_hash, pindex->nHeight); + + for (const auto& iter : pending_beacon_hashes) { + uint256 activated_beacon_hash = Hash(pindex->GetBlockHash(), iter); + + GRC::Beacon_ptr activated_beacon = registry.FindHistorical(activated_beacon_hash); + + if (activated_beacon != nullptr) { + std::cout << "activated beacon record: " + << "blockheight = " << pindex->nHeight + << ", cpid = " << activated_beacon->m_cpid.ToString() + << ", public key = " << HexStr(activated_beacon->m_public_key) + << ", address = " << activated_beacon->GetAddress().ToString() + << ", timestamp = " << activated_beacon->m_timestamp + << ", hash = " << activated_beacon->m_hash.GetHex() + << ", prev beacon hash = " << activated_beacon->m_previous_hash.GetHex() + << ", status = " << activated_beacon->StatusToString() + << std::endl; + } + } + + for (const auto& iter : registry.ExpiredBeacons()) { + if (iter != nullptr) { + std::cout << "expired beacon record: " + << "blockheight = " << pindex->nHeight + << ", cpid = " << iter->m_cpid.ToString() + << ", public key = " << HexStr(iter->m_public_key) + << ", address = " << iter->GetAddress().ToString() + << ", timestamp = " << iter->m_timestamp + << ", hash = " << iter->m_hash.GetHex() + << ", prev beacon hash = " << iter->m_previous_hash.GetHex() + << ", status = " << iter->StatusToString() + << std::endl; + } + + } } } @@ -224,6 +325,16 @@ class BeaconRegistryTest // Create a copy of the referenced beacon object with a shared pointer to it and store. m_local_historical_beacon_map_init[hash] = std::make_shared(*beacon_ptr); + std::cout << "init beacon db record: " + << ", cpid = " << beacon_ptr->m_cpid.ToString() + << ", public key = " << HexStr(beacon_ptr->m_public_key) + << ", address = " << beacon_ptr->GetAddress().ToString() + << ", timestamp = " << beacon_ptr->m_timestamp + << ", hash = " << beacon_ptr->m_hash.GetHex() + << ", prev beacon hash = " << beacon_ptr->m_previous_hash.GetHex() + << ", status = " << beacon_ptr->StatusToString() + << std::endl; + init_beacon_db_iter = init_beacon_db.advance(init_beacon_db_iter); } @@ -265,6 +376,16 @@ class BeaconRegistryTest // Create a copy of the referenced beacon object with a shared pointer to it and store. m_local_historical_beacon_map_reinit[hash] = std::make_shared(*beacon_ptr); + std::cout << "init beacon db record: " + << ", cpid = " << beacon_ptr->m_cpid.ToString() + << ", public key = " << HexStr(beacon_ptr->m_public_key) + << ", address = " << beacon_ptr->GetAddress().ToString() + << ", timestamp = " << beacon_ptr->m_timestamp + << ", hash = " << beacon_ptr->m_hash.GetHex() + << ", prev beacon hash = " << beacon_ptr->m_previous_hash.GetHex() + << ", status = " << beacon_ptr->StatusToString() + << std::endl; + reinit_beacon_db_iter = reinit_beacon_db.advance(reinit_beacon_db_iter); } }; @@ -314,7 +435,7 @@ class BeaconRegistryTest << ", timestamp = " << left.second->m_timestamp << ", hash = " << left.second->m_hash.GetHex() << ", prev beacon hash = " << left.second->m_previous_hash.GetHex() - << ", status = " << ToString(left.second->m_status.Raw()) + << ", status = " << left.second->StatusToString() << std::endl; } @@ -348,8 +469,8 @@ class BeaconRegistryTest std::cout << "init_beacon prev beacon hash = " << left_beacon_ptr->m_previous_hash.GetHex() << ", reinit_beacon prev beacon hash = " << right_beacon_iter->second->m_previous_hash.GetHex() << std::endl; - std::cout << "init_beacon status = " << ToString(left_beacon_ptr->m_status.Raw()) - << ", reinit_beacon status = " << ToString(right_beacon_iter->second->m_status.Raw()) << std::endl; + std::cout << "init_beacon status = " << left_beacon_ptr->StatusToString() + << ", reinit_beacon status = " << right_beacon_iter->second->StatusToString() << std::endl; } } @@ -376,7 +497,7 @@ class BeaconRegistryTest << ", timestamp = " << left.second->m_timestamp << ", hash = " << left.second->m_hash.GetHex() << ", prev beacon hash = " << left.second->m_previous_hash.GetHex() - << ", status = " << ToString(left.second->m_status.Raw()) + << ", status = " << left.second->StatusToString() << std::endl; } @@ -410,8 +531,8 @@ class BeaconRegistryTest std::cout << "reinit_beacon prev beacon hash = " << left_beacon_ptr->m_previous_hash.GetHex() << ", init_beacon prev beacon hash = " << right_beacon_iter->second->m_previous_hash.GetHex() << std::endl; - std::cout << "reinit_beacon status = " << ToString(left_beacon_ptr->m_status.Raw()) - << ", init_beacon status = " << ToString(right_beacon_iter->second->m_status.Raw()) << std::endl; + std::cout << "reinit_beacon status = " << left_beacon_ptr->StatusToString() + << ", init_beacon status = " << right_beacon_iter->second->StatusToString() << std::endl; } } @@ -440,7 +561,7 @@ class BeaconRegistryTest << ", timestamp = " << beacon.m_timestamp << ", hash = " << beacon.m_hash.GetHex() << ", prev beacon hash = " << beacon.m_previous_hash.GetHex() - << ", status = " << ToString(beacon.m_status.Raw()) + << ", status = " << beacon.StatusToString() << std::endl; } @@ -457,7 +578,7 @@ class BeaconRegistryTest << ", timestamp = " << beacon.m_timestamp << ", hash = " << beacon.m_hash.GetHex() << ", prev beacon hash = " << beacon.m_previous_hash.GetHex() - << ", status = " << ToString(beacon.m_status.Raw()) + << ", status = " << beacon.StatusToString() << std::endl; } } @@ -486,7 +607,7 @@ class BeaconRegistryTest << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", status = " << left.second.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -512,8 +633,8 @@ class BeaconRegistryTest std::cout << "init_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() << ", reinit_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << "init_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << "init_beacon status = " << left_beacon.StatusToString() + << ", reinit_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -539,7 +660,7 @@ class BeaconRegistryTest << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", status = " << left.second.StatusToString() << std::endl; } @@ -566,8 +687,8 @@ class BeaconRegistryTest std::cout << "reinit_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() << ", init_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << "reinit_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", init_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << "reinit_beacon status = " << left_beacon.StatusToString() + << ", init_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -603,7 +724,7 @@ class BeaconRegistryTest << ", timestamp = " << left_beacon.m_timestamp << ", hash = " << left_beacon.m_hash.GetHex() << ", prev beacon hash = " << left_beacon.m_previous_hash.GetHex() - << ", status = " << ToString(left_beacon.m_status.Raw()) + << ", status = " << left_beacon.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -633,8 +754,8 @@ class BeaconRegistryTest << ", reinit_pending_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << ", init_pending_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_pending_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << ", init_pending_beacon status = " << left_beacon.StatusToString() + << ", reinit_pending_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -658,7 +779,7 @@ class BeaconRegistryTest << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", status = " << left.second.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -688,8 +809,8 @@ class BeaconRegistryTest << ", reinit_pending_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << ", init_pending_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_pending_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << ", init_pending_beacon status = " << left_beacon.StatusToString() + << ", reinit_pending_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -1128,4 +1249,415 @@ BOOST_AUTO_TEST_CASE(beaconstorage_mainnet_test) beacon_registry_test.BeaconDatabaseComparisonChecks_m_pending(); } +BOOST_AUTO_TEST_CASE(beacon_registry_GetBeaconChainletRoot_test) +{ + LogInstance().EnableCategory(BCLog::LogFlags::BEACON); + LogInstance().EnableCategory(BCLog::LogFlags::ACCRUAL); + + FastRandomContext rng(uint256 {0}); + + GRC::BeaconRegistry& registry = GRC::GetBeaconRegistry(); + + // Make sure the registry is reset. + registry.Reset(); + + // This is a trivial initial pending beacon, activation, and two renewals. The typical type of beacon chainlet. + + // Pending beacon + CTransaction tx1 {}; + tx1.nTime = int64_t {1}; + uint256 tx1_hash = tx1.GetHash(); + + CBlockIndex* pindex1 = new CBlockIndex; + pindex1->nVersion = 13; + pindex1->nHeight = 1; + pindex1->nTime = tx1.nTime; + + GRC::Beacon beacon1 {TestKey::Public(), tx1.nTime, tx1_hash}; + beacon1.m_cpid = TestKey::Cpid(); + beacon1.m_status = GRC::Beacon::Status {GRC::BeaconStatusForStorage::PENDING}; + GRC::BeaconPayload beacon_payload1 {2, TestKey::Cpid(), beacon1}; + beacon_payload1.m_signature = TestKey::Signature(beacon_payload1); + + GRC::Contract contract1 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload1); + GRC::ContractContext ctx1 {contract1, tx1, pindex1}; + + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_cpid == TestKey::Cpid()); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_hash == tx1_hash); + BOOST_CHECK(ctx1.m_tx.GetHash() == tx1_hash); + + registry.Add(ctx1); + + BOOST_CHECK(registry.GetBeaconDB().size() == 1); + + std::vector pending_beacons = registry.FindPending(TestKey::Cpid()); + + BOOST_CHECK(pending_beacons.size() == 1); + + if (pending_beacons.size() == 1) { + BOOST_CHECK(pending_beacons[0]->m_cpid == TestKey::Cpid()); + BOOST_CHECK(pending_beacons[0]->m_hash == tx1_hash); + BOOST_CHECK(pending_beacons[0]->m_previous_hash == uint256 {}); + BOOST_CHECK(pending_beacons[0]->m_public_key == TestKey::Public()); + BOOST_CHECK(pending_beacons[0]->m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(pending_beacons[0]->m_timestamp == tx1.nTime); + } + + // Activation + CBlockIndex* pindex2 = new CBlockIndex; + pindex2->nVersion = 13; + pindex2->nHeight = 2; + pindex2->nTime = int64_t {2}; + uint256* block2_phash = new uint256 {rng.rand256()}; + pindex2->phashBlock = block2_phash; + + std::vector beacon_ids {TestKey::Public().GetID()}; + + registry.ActivatePending(beacon_ids, pindex2->nTime, *pindex2->phashBlock, pindex2->nHeight); + + uint256 activated_beacon_hash = Hash(*block2_phash, pending_beacons[0]->m_hash); + + BOOST_CHECK(registry.GetBeaconDB().size() == 2); + + GRC::Beacon_ptr chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == activated_beacon_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::ACTIVE); + // Note that the activated beacon's timestamp is actually the same as the timestamp of the PENDING beacon. (Here + // t = 1; + BOOST_CHECK(chainlet_head->m_timestamp == 1); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + // There is only one entry in the chainlet.. so the head and root are the same. + BOOST_CHECK(chainlet_root->m_hash == chainlet_head->m_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 1); + } + + // Renewal + CTransaction tx3 {}; + tx3.nTime = int64_t {3}; + uint256 tx3_hash = tx3.GetHash(); + CBlockIndex index3 {}; + index3.nVersion = 13; + index3.nHeight = 3; + index3.nTime = tx3.nTime; + + GRC::Beacon beacon3 {TestKey::Public(), tx3.nTime, tx3_hash}; + beacon3.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload3 {2, TestKey::Cpid(), beacon3}; + beacon_payload3.m_signature = TestKey::Signature(beacon_payload3); + + GRC::Contract contract3 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload3); + GRC::ContractContext ctx3 {contract3, tx3, &index3}; + + registry.Add(ctx3); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == tx3_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 2); + } + + // Second renewal + CTransaction tx4 {}; + tx4.nTime = int64_t {4}; + uint256 tx4_hash = tx4.GetHash(); + CBlockIndex index4 = {}; + index4.nVersion = 13; + index4.nHeight = 2; + index4.nTime = tx4.nTime; + + GRC::Beacon beacon4 {TestKey::Public(), tx4.nTime, tx4_hash}; + beacon4.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload4 {2, TestKey::Cpid(), beacon4}; + beacon_payload4.m_signature = TestKey::Signature(beacon_payload4); + + GRC::Contract contract4 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload4); + GRC::ContractContext ctx4 {contract4, tx4, &index4}; + registry.Add(ctx4); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + BOOST_CHECK(chainlet_head->m_hash == tx4_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 3); + } + + // Let's corrupt the activation beacon to have a previous beacon hash that refers circularly back to the chain head... + bool original_activated_beacon_found = true; + bool circular_corruption_detected = false; + + if (GRC::Beacon_ptr first_active = registry.FindHistorical(activated_beacon_hash)) { + // The original activated beacon m_previous_hash should be the pending beacon hash (beacon1). + BOOST_CHECK(first_active->m_previous_hash == beacon1.m_hash); + BOOST_CHECK(first_active->m_status == GRC::BeaconStatusForStorage::ACTIVE); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + // This creates a circular chainlet. + first_active->m_previous_hash = chainlet_head->m_hash; + + beacon_chain_out_ptr->clear(); + + try { + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + circular_corruption_detected = true; + } + } else { + original_activated_beacon_found = false; + } + + BOOST_CHECK_EQUAL(original_activated_beacon_found, true); + + if (original_activated_beacon_found) { + BOOST_CHECK_EQUAL(circular_corruption_detected, true); + } +} + +BOOST_AUTO_TEST_CASE(beacon_registry_GetBeaconChainletRoot_test_2) +{ + // For right now we will just cut and paste from above, given that the circularity detection causes the registry + // to get reset. + + LogInstance().EnableCategory(BCLog::LogFlags::BEACON); + LogInstance().EnableCategory(BCLog::LogFlags::ACCRUAL); + + FastRandomContext rng(uint256 {0}); + + GRC::BeaconRegistry& registry = GRC::GetBeaconRegistry(); + + // Make sure the registry is reset. + registry.Reset(); + + // This is a trivial initial pending beacon, activation, and two renewals. The typical type of beacon chainlet. + + // Pending beacon + CTransaction tx1 {}; + tx1.nTime = int64_t {1}; + uint256 tx1_hash = tx1.GetHash(); + + CBlockIndex* pindex1 = new CBlockIndex; + pindex1->nVersion = 13; + pindex1->nHeight = 1; + pindex1->nTime = tx1.nTime; + + GRC::Beacon beacon1 {TestKey::Public(), tx1.nTime, tx1_hash}; + beacon1.m_cpid = TestKey::Cpid(); + beacon1.m_status = GRC::Beacon::Status {GRC::BeaconStatusForStorage::PENDING}; + GRC::BeaconPayload beacon_payload1 {2, TestKey::Cpid(), beacon1}; + beacon_payload1.m_signature = TestKey::Signature(beacon_payload1); + + GRC::Contract contract1 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload1); + GRC::ContractContext ctx1 {contract1, tx1, pindex1}; + + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_cpid == TestKey::Cpid()); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_hash == tx1_hash); + BOOST_CHECK(ctx1.m_tx.GetHash() == tx1_hash); + + registry.Add(ctx1); + + BOOST_CHECK(registry.GetBeaconDB().size() == 1); + + std::vector pending_beacons = registry.FindPending(TestKey::Cpid()); + + BOOST_CHECK(pending_beacons.size() == 1); + + if (pending_beacons.size() == 1) { + BOOST_CHECK(pending_beacons[0]->m_cpid == TestKey::Cpid()); + BOOST_CHECK(pending_beacons[0]->m_hash == tx1_hash); + BOOST_CHECK(pending_beacons[0]->m_previous_hash == uint256 {}); + BOOST_CHECK(pending_beacons[0]->m_public_key == TestKey::Public()); + BOOST_CHECK(pending_beacons[0]->m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(pending_beacons[0]->m_timestamp == tx1.nTime); + } + + // Activation + CBlockIndex* pindex2 = new CBlockIndex; + pindex2->nVersion = 13; + pindex2->nHeight = 2; + pindex2->nTime = int64_t {2}; + uint256* block2_phash = new uint256 {rng.rand256()}; + pindex2->phashBlock = block2_phash; + + std::vector beacon_ids {TestKey::Public().GetID()}; + + registry.ActivatePending(beacon_ids, pindex2->nTime, *pindex2->phashBlock, pindex2->nHeight); + + uint256 activated_beacon_hash = Hash(*block2_phash, pending_beacons[0]->m_hash); + + BOOST_CHECK(registry.GetBeaconDB().size() == 2); + + GRC::Beacon_ptr chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == activated_beacon_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::ACTIVE); + // Note that the activated beacon's timestamp is actually the same as the timestamp of the PENDING beacon. (Here + // t = 1; + BOOST_CHECK(chainlet_head->m_timestamp == 1); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + // There is only one entry in the chainlet.. so the head and root are the same. + BOOST_CHECK(chainlet_root->m_hash == chainlet_head->m_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 1); + } + + // Renewal + CTransaction tx3 {}; + tx3.nTime = int64_t {3}; + uint256 tx3_hash = tx3.GetHash(); + CBlockIndex index3 {}; + index3.nVersion = 13; + index3.nHeight = 3; + index3.nTime = tx3.nTime; + + GRC::Beacon beacon3 {TestKey::Public(), tx3.nTime, tx3_hash}; + beacon3.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload3 {2, TestKey::Cpid(), beacon3}; + beacon_payload3.m_signature = TestKey::Signature(beacon_payload3); + + GRC::Contract contract3 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload3); + GRC::ContractContext ctx3 {contract3, tx3, &index3}; + + registry.Add(ctx3); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == tx3_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 2); + } + + // Second renewal + CTransaction tx4 {}; + tx4.nTime = int64_t {4}; + uint256 tx4_hash = tx4.GetHash(); + CBlockIndex index4 = {}; + index4.nVersion = 13; + index4.nHeight = 2; + index4.nTime = tx4.nTime; + + GRC::Beacon beacon4 {TestKey::Public(), tx4.nTime, tx4_hash}; + beacon4.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload4 {2, TestKey::Cpid(), beacon4}; + beacon_payload4.m_signature = TestKey::Signature(beacon_payload4); + + GRC::Contract contract4 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload4); + GRC::ContractContext ctx4 {contract4, tx4, &index4}; + registry.Add(ctx4); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + BOOST_CHECK(chainlet_head->m_hash == tx4_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 3); + } + + // Let's corrupt the activation beacon to have a previous beacon hash that is the same as its hash... + bool original_activated_beacon_found = true; + bool circular_corruption_detected = false; + + if (GRC::Beacon_ptr first_active = registry.FindHistorical(activated_beacon_hash)) { + // The original activated beacon m_previous_hash should be the pending beacon hash (beacon1). + BOOST_CHECK(first_active->m_previous_hash == beacon1.m_hash); + BOOST_CHECK(first_active->m_status == GRC::BeaconStatusForStorage::ACTIVE); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + // This creates a immediately circular chainlet of one. + first_active->m_previous_hash = first_active->m_hash; + + beacon_chain_out_ptr->clear(); + + try { + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + circular_corruption_detected = true; + } + } else { + original_activated_beacon_found = false; + } + + BOOST_CHECK_EQUAL(original_activated_beacon_found, true); + + if (original_activated_beacon_found) { + BOOST_CHECK_EQUAL(circular_corruption_detected, true); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/claim_tests.cpp b/src/test/gridcoin/claim_tests.cpp index 0a9b5f1af4..33d1e204ad 100644 --- a/src/test/gridcoin/claim_tests.cpp +++ b/src/test/gridcoin/claim_tests.cpp @@ -97,6 +97,26 @@ static CKey GetTestPrivateKey() return key; } + +// Unfortunately, GCC 13 on openSUSE i386 is misbehaving and exhibiting weird errors in the last decimal places for things +// even as straightforward as +// +// double foo = 0.0; +// text >> foo. +// +// This comparison function works around that by allowing a small error band to pass the tests, but not enough to invalidate +// the tests on compilers that work. +bool comp_double(double lhs, double rhs) +{ + // Require exact match if 0=0. + if (std::min(lhs, rhs) == 0.0) { + return (lhs == rhs); + } else { + double unsigned_rel_error = std::abs(lhs - rhs) / std::min(lhs, rhs); + + return (unsigned_rel_error <= double {1e-8}); + } +} } // anonymous namespace // ----------------------------------------------------------------------------- @@ -118,7 +138,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_to_an_empty_claim) BOOST_CHECK(claim.m_magnitude == 0); BOOST_CHECK(claim.m_research_subsidy == 0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); @@ -141,7 +161,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_to_the_specified_version) BOOST_CHECK(claim.m_magnitude == 0); BOOST_CHECK(claim.m_research_subsidy == 0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); @@ -220,7 +240,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_legacy_boincblock_string_for_researcher) BOOST_CHECK(claim.m_magnitude == 123); BOOST_CHECK(claim.m_research_subsidy == 47.25 * COIN); - BOOST_CHECK(claim.m_magnitude_unit == 0.123456); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.123456)); BOOST_CHECK(claim.m_signature == signature); @@ -503,7 +523,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor) BOOST_CHECK(claim.m_research_subsidy == 0); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); BOOST_CHECK(claim.m_quorum_address.empty() == true); BOOST_CHECK(claim.m_superblock->WellFormed() == false); @@ -545,7 +565,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor_with_superblock) BOOST_CHECK(claim.m_research_subsidy == 0); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); } @@ -630,7 +650,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher) BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature == expected.m_signature); BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); @@ -670,7 +690,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher_with_superbloc BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature == expected.m_signature); BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); diff --git a/src/test/gridcoin/project_tests.cpp b/src/test/gridcoin/project_tests.cpp index 66fabbd723..900ae1babe 100644 --- a/src/test/gridcoin/project_tests.cpp +++ b/src/test/gridcoin/project_tests.cpp @@ -9,25 +9,6 @@ #include namespace { -//! -//! \brief Generate a mock project contract. -//! -//! \param key A fake project name as it might appear in a contract. -//! \param value A fake project URL as it might appear in a contract. -//! -//! \return A mock project contract. -//! -GRC::Contract contract(std::string key, std::string value) -{ - return GRC::MakeContract( - // Add or delete checked before passing to handler, so we don't need - // to give a specific value here: - GRC::ContractAction::UNKNOWN, - std::move(key), - std::move(value), - 1234567); // timestamp -} - void AddProjectEntry(const uint32_t& payload_version, const std::string& name, const std::string& url, const bool& gdpr_status, const int& height, const uint64_t time, const bool& reset_registry = false) { diff --git a/src/test/gridcoin/researcher_tests.cpp b/src/test/gridcoin/researcher_tests.cpp index 352c2b464f..e163fb5862 100644 --- a/src/test/gridcoin/researcher_tests.cpp +++ b/src/test/gridcoin/researcher_tests.cpp @@ -183,6 +183,26 @@ void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, c registry.Add({contract, dummy_tx, &dummy_index}); } + +// Unfortunately, GCC 13 on openSUSE i386 is misbehaving and exhibiting weird errors in the last decimal places for things +// even as straightforward as +// +// double foo = 0.0; +// text >> foo. +// +// This comparison function works around that by allowing a small error band to pass the tests, but not enough to invalidate +// the tests on compilers that work. +bool comp_double(double lhs, double rhs) +{ + // Require exact match if 0=0. + if (std::min(lhs, rhs) == 0.0) { + return (lhs == rhs); + } else { + double unsigned_rel_error = std::abs(lhs - rhs) / std::min(lhs, rhs); + + return (unsigned_rel_error <= double {1e-8}); + } +} } // anonymous namespace // ----------------------------------------------------------------------------- @@ -205,7 +225,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_with_project_data) BOOST_CHECK(project.m_cpid == expected); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "url"); - BOOST_CHECK(project.m_rac == 0.0); + BOOST_CHECK(comp_double(project.m_rac, 0.0)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); } @@ -234,7 +254,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_project_xml_string) BOOST_CHECK(project.m_cpid == cpid); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "https://example.com/"); - BOOST_CHECK(project.m_rac == 123.45); + BOOST_CHECK(comp_double(project.m_rac, 123.45)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); // Clean up: @@ -269,7 +289,7 @@ BOOST_AUTO_TEST_CASE(it_falls_back_to_compute_a_missing_external_cpid) BOOST_CHECK(project.m_cpid == cpid); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "https://example.com/"); - BOOST_CHECK(project.m_rac == 123.45); + BOOST_CHECK(comp_double(project.m_rac, 123.45)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); // Clean up: @@ -488,7 +508,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_set_of_project_xml_sections) BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); BOOST_CHECK(project1->m_url == "https://example.com/1"); - BOOST_CHECK(project1->m_rac == 123.45); + BOOST_CHECK(comp_double(project1->m_rac, 123.45)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -500,7 +520,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_set_of_project_xml_sections) BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); BOOST_CHECK(project2->m_url == "https://example.com/2"); - BOOST_CHECK(project2->m_rac == 567.89); + BOOST_CHECK(comp_double(project2->m_rac, 567.89)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -855,7 +875,7 @@ BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); BOOST_CHECK(project1->m_url == "https://example.com/1"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -867,7 +887,7 @@ BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); BOOST_CHECK(project2->m_url == "https://example.com/2"); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -909,7 +929,7 @@ BOOST_AUTO_TEST_CASE(it_looks_up_loaded_boinc_projects_by_name) BOOST_CHECK(project->m_cpid == cpid); BOOST_CHECK(project->m_team == "gridcoin"); BOOST_CHECK(project->m_url == "https://example.com/"); - BOOST_CHECK(project->m_rac == 1.1); + BOOST_CHECK(comp_double(project->m_rac, 1.1)); BOOST_CHECK(project->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project->Eligible() == true); } else { @@ -1048,7 +1068,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "not gridcoin"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project1->Eligible() == false); } else { @@ -1059,7 +1079,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project2->m_name == "project name 2"); BOOST_CHECK(project2->m_cpid == cpid); BOOST_CHECK(project2->m_team.empty() == true); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project2->Eligible() == false); } else { @@ -1070,7 +1090,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project3->m_name == "project name 3"); BOOST_CHECK(project3->m_cpid == GRC::Cpid()); BOOST_CHECK(project3->m_team == "gridcoin"); - BOOST_CHECK(project3->m_rac == 3.3); + BOOST_CHECK(comp_double(project3->m_rac, 3.3)); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::MALFORMED_CPID); BOOST_CHECK(project3->Eligible() == false); } else { @@ -1081,7 +1101,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project4->m_name == "project name 4"); BOOST_CHECK(project4->m_cpid == GRC::Cpid()); BOOST_CHECK(project4->m_team == "gridcoin"); - BOOST_CHECK(project4->m_rac == 4.4); + BOOST_CHECK(comp_double(project4->m_rac, 4.4)); BOOST_CHECK(project4->m_error == GRC::MiningProject::Error::MALFORMED_CPID); BOOST_CHECK(project4->Eligible() == false); } else { @@ -1092,7 +1112,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project5->m_name == "project name 5"); BOOST_CHECK(project5->m_cpid == cpid); BOOST_CHECK(project5->m_team == "gridcoin"); - BOOST_CHECK(project5->m_rac == 5.5); + BOOST_CHECK(comp_double(project5->m_rac, 5.5)); BOOST_CHECK(project5->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project5->Eligible() == false); } else { @@ -1103,7 +1123,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project6->m_name == "project name 6"); BOOST_CHECK(project6->m_cpid == cpid); BOOST_CHECK(project6->m_team == "gridcoin"); - BOOST_CHECK(project6->m_rac == 6.6); + BOOST_CHECK(comp_double(project6->m_rac, 6.6)); BOOST_CHECK(project6->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project6->Eligible() == false); } else { @@ -1112,7 +1132,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) if (const GRC::ProjectOption project7 = projects.Try("project name 7")) { BOOST_CHECK(project7->m_name == "project name 7"); - BOOST_CHECK(project7->m_rac == 7.7); + BOOST_CHECK(comp_double(project7->m_rac, 7.7)); BOOST_CHECK(project7->m_error == GRC::MiningProject::Error::POOL); BOOST_CHECK(project7->Eligible() == false); } else { @@ -1122,7 +1142,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) if (const GRC::ProjectOption project8 = projects.Try("project name 8")) { BOOST_CHECK(project8->m_name == "project name 8"); BOOST_CHECK(project8->m_cpid.IsZero() == true); - BOOST_CHECK(project8->m_rac == 8.8); + BOOST_CHECK(comp_double(project8->m_rac, 8.8)); BOOST_CHECK(project8->m_error == GRC::MiningProject::Error::POOL); BOOST_CHECK(project8->Eligible() == false); } else { @@ -1133,7 +1153,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project9->m_name == "project name 9"); BOOST_CHECK(project9->m_cpid == cpid); BOOST_CHECK(project9->m_team == "not gridcoin"); - BOOST_CHECK(project9->m_rac == 0.0); + BOOST_CHECK(comp_double(project9->m_rac, 0.0)); BOOST_CHECK(project9->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project9->Eligible() == false); } else { @@ -1212,7 +1232,7 @@ BOOST_AUTO_TEST_CASE(it_skips_the_team_requirement_when_set_by_protocol) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "! not gridcoin !"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -1282,7 +1302,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "! not gridcoin !"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project1->Eligible() == false); } else { @@ -1293,7 +1313,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project2->m_name == "project name 2"); BOOST_CHECK(project2->m_cpid == cpid); BOOST_CHECK(project2->m_team == "team 1"); - BOOST_CHECK(project2->m_rac == 0); + BOOST_CHECK(comp_double(project2->m_rac, 0)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -1304,7 +1324,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project3->m_name == "project name 3"); BOOST_CHECK(project3->m_cpid == cpid); BOOST_CHECK(project3->m_team == "team 2"); - BOOST_CHECK(project3->m_rac == 0); + BOOST_CHECK(comp_double(project3->m_rac, 0)); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project3->Eligible() == true); } else { @@ -1629,7 +1649,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project1->m_name == "valid project 1"); BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_url == "https://project1.example.com/boinc/"); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); @@ -1641,7 +1661,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project2->m_name == "valid project 2"); BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_url == "https://project2.example.com/boinc/"); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); @@ -1654,7 +1674,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project3->m_name == "invalid project 3"); BOOST_CHECK(project3->m_cpid == cpid_2); BOOST_CHECK(project3->m_team == "gridcoin"); - BOOST_CHECK(project3->m_rac == 3.3); + BOOST_CHECK(comp_double(project3->m_rac, 3.3)); BOOST_CHECK(project3->m_url == "https://project3.example.com/boinc/"); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project3->Eligible() == false); diff --git a/src/test/gridcoin/sidestake_tests.cpp b/src/test/gridcoin/sidestake_tests.cpp index 590d2a43f9..c0caa711f5 100644 --- a/src/test/gridcoin/sidestake_tests.cpp +++ b/src/test/gridcoin/sidestake_tests.cpp @@ -140,7 +140,7 @@ BOOST_AUTO_TEST_CASE(sidestake_Allocation_multiplication_and_derivation_of_alloc CAmount max_accrual = 16384 * COIN; - CAmount actual_output = static_cast(allocation * max_accrual).ToCAmount(); + CAmount actual_output = (allocation * max_accrual).ToCAmount(); BOOST_CHECK_EQUAL(actual_output, int64_t {1638236160000}); } diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 2a35324da8..9711966abd 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -282,7 +282,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, 0)); keys.clear(); - keys.push_back(key2); keys.push_back(key2); // Can't re-use sig + keys.push_back(key2); keys.push_back(key2); // Can't reuse sig CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, 0)); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 7c29dcf892..8f8bd204f2 100755 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1385,6 +1385,24 @@ BOOST_AUTO_TEST_CASE(util_Fraction_addition_with_internal_simplification) BOOST_CHECK_EQUAL(sum.IsSimplified(), true); } +BOOST_AUTO_TEST_CASE(util_Fraction_addition_with_internal_gcd_simplification) +{ + Fraction lhs(1, 6); + Fraction rhs(2, 15); + + // gcd(6, 15) = 3, so this really is + // + // 1 * (15/3) + 2 * (6/3) 1 * 5 + 2 * 2 3 + // ---------------------- = ------------- = -- + // 3 * (6/3) * (15/3) 3 * 2 * 5 10 + + Fraction sum = lhs + rhs; + + BOOST_CHECK_EQUAL(sum.GetNumerator(), 3); + BOOST_CHECK_EQUAL(sum.GetDenominator(), 10); + BOOST_CHECK_EQUAL(sum.IsSimplified(), true); +} + BOOST_AUTO_TEST_CASE(util_Fraction_subtraction) { Fraction lhs(2, 3); @@ -1421,6 +1439,29 @@ BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_with_internal_simplification) BOOST_CHECK_EQUAL(product.IsSimplified(), true); } +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_with_cross_simplification_overflow_resistance) +{ + + Fraction lhs(std::numeric_limits::max() - 3, std::numeric_limits::max() - 1, false); + Fraction rhs((std::numeric_limits::max() - 1) / (int64_t) 2, (std::numeric_limits::max() - 3) / (int64_t) 2); + + Fraction product; + + // This should NOT overflow + bool overflow = false; + try { + product = lhs * rhs; + } catch (std::overflow_error& e) { + overflow = true; + } + + BOOST_CHECK_EQUAL(overflow, false); + + if (!overflow) { + BOOST_CHECK(product == Fraction(1)); + } +} + BOOST_AUTO_TEST_CASE(util_Fraction_division_with_internal_simplification) { Fraction lhs(-2, 3); diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp index 9135984c56..c5e390e531 100755 --- a/src/test/wallet_tests.cpp +++ b/src/test/wallet_tests.cpp @@ -1,5 +1,6 @@ #include +#include "gridcoin/sidestake.h" #include "main.h" #include "wallet/wallet.h" @@ -66,29 +67,29 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) empty_wallet(); // with an empty wallet we can't even pay one cent - BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); - add_coin(1*CENT, 4); // add a new 1 cent coin + add_coin(CENT, 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent - BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); // but we can find a new 1 cent - BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); - add_coin(2*CENT); // add a mature 2 cent coin + add_coin(2 * CENT); // add a mature 2 cent coin // we can't make 3 cents of mature coins - BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(3 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); // we can make 3 cents of new coins - BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(3 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); - add_coin(5*CENT); // add a mature 5 cent coin, - add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses - add_coin(20*CENT); // and a mature 20 cent coin + add_coin(5 * CENT); // add a mature 5 cent coin, + add_coin(10 * CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(20 * CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 @@ -97,109 +98,109 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // we can't even make 37 cents if we don't allow new coins even if they're from us BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, spendTime, 6, 6, vCoins, setCoinsRet, nValueRet)); // but we can make 37 cents if we accept new coins from ourself - BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(37 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); // and we can make 38 cents if we accept all new coins - BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(38 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents - BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + BOOST_CHECK(wallet.SelectCoinsMinConf(34 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents + // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 - BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(7 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. - BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(8 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(nValueRet == 8 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) - BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(9 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin empty_wallet(); - add_coin( 6*CENT); - add_coin( 7*CENT); - add_coin( 8*CENT); - add_coin(20*CENT); - add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total + add_coin(6 * CENT); + add_coin(7 * CENT); + add_coin(8 * CENT); + add_coin(20 * CENT); + add_coin(30 * CENT); // now we have 6+7+8+20+30 = 71 cents total // check that we have 71 and not 72 - BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(71 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); - add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + add_coin(5 * CENT); // now we have 5+6+7+8+20+30 = 75 cents total // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); - add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 + add_coin(18 * CENT); // now we have 5+6+7+8+18+20+30 // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // because in the event of a tie, the biggest coin wins // now try making 11 cents. we should get 5+6 - BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(11 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // check that the smallest bigger coin is used - add_coin( 1*COIN); - add_coin( 2*COIN); - add_coin( 3*COIN); - add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + add_coin(1 * COIN); + add_coin(2 * COIN); + add_coin(3 * COIN); + add_coin(4 * COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + BOOST_CHECK(wallet.SelectCoinsMinConf(95 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); - BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(195 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance empty_wallet(); - add_coin(0.1*CENT); - add_coin(0.2*CENT); - add_coin(0.3*CENT); - add_coin(0.4*CENT); - add_coin(0.5*CENT); + add_coin((GRC::Allocation(1, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(2, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(3, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(4, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(5, 10, true) * CENT).ToCAmount()); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents // we'll get sub-cent change whatever happens, so can expect 1.0 exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // but if we add a bigger coin, making it possible to avoid sub-cent change, things change: - add_coin(1111*CENT); + add_coin(1111 * CENT); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount - + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount // if we add more sub-cent coins: - add_coin(0.6*CENT); - add_coin(0.7*CENT); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(7, 10, true) * CENT).ToCAmount()); // and try again to make 1.0 cents, we can still make 1.0 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change @@ -207,7 +208,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) for (int i = 0; i < 20; i++) add_coin(50000 * COIN); - BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(500000 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 10); // in ten coins @@ -216,38 +217,43 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // sometimes it will fail, and so we use the next biggest coin: empty_wallet(); - add_coin(0.5 * CENT); - add_coin(0.6 * CENT); - add_coin(0.7 * CENT); + add_coin((GRC::Allocation(5, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(7, 10, true) * CENT).ToCAmount()); add_coin(1111 * CENT); - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) empty_wallet(); - add_coin(0.4 * CENT); - add_coin(0.6 * CENT); - add_coin(0.8 * CENT); + add_coin((GRC::Allocation(4, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(8, 10, true) * CENT).ToCAmount()); add_coin(1111 * CENT); - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // in two coins 0.4+0.6 // test avoiding sub-cent change empty_wallet(); - add_coin(0.0005 * COIN); - add_coin(0.01 * COIN); - add_coin(1 * COIN); + // Use rational arithmetic because the floating point has problems with GCC13 on 32 bit architecture x86. + add_coin((GRC::Allocation(5, 10000, true) * COIN).ToCAmount()); + add_coin((GRC::Allocation(1, 100, true) * COIN).ToCAmount()); + add_coin(COIN); // trying to make 1.0001 from these three coins - BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins + BOOST_CHECK(wallet.SelectCoinsMinConf((GRC::Allocation(10001, 10000, true) * COIN).ToCAmount(), + spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + // we should get all coins + BOOST_CHECK(nValueRet == (GRC::Allocation(10105, 10000, true) * COIN).ToCAmount()); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change - BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01 + BOOST_CHECK(wallet.SelectCoinsMinConf((GRC::Allocation(999, 1000, true) * COIN).ToCAmount(), + spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + // we should get 1 + 0.01 + BOOST_CHECK(nValueRet == (GRC::Allocation(101, 100, true) * COIN).ToCAmount()); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // test randomness @@ -277,15 +283,15 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // add 75 cents in small change. not enough to make 90 cents, // then try making 90 cents. there are multiple competing "smallest bigger" coins, // one of which should be picked at random - add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT); + add_coin(5 * CENT); add_coin(10 * CENT); add_coin(15 * CENT); add_coin(20 * CENT); add_coin(25 * CENT); fails = 0; for (int i = 0; i < RANDOM_REPEATS; i++) { // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time // run the test RANDOM_REPEATS times and only complain if all of them fail - BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, spendTime, 1, 6, vCoins, setCoinsRet , nValueRet)); - BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, spendTime, 1, 6, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(90 * CENT, spendTime, 1, 6, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(90 * CENT, spendTime, 1, 6, vCoins, setCoinsRet2, nValueRet)); if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; } diff --git a/src/test/xxd/xxd.c b/src/test/xxd/xxd.c index e2fc9dff6a..ae25feed2b 100644 --- a/src/test/xxd/xxd.c +++ b/src/test/xxd/xxd.c @@ -36,7 +36,7 @@ * 7.06.96 -i printed 'int' instead of 'char'. *blush* * added Bram's OS2 ifdefs... * 18.07.96 gcc -Wall @ SunOS4 is now silent. - * Added osver for MSDOS/DJGPP/WIN32. + * Added osver for MS-DOS/DJGPP/WIN32. * 29.08.96 Added size_t to strncmp() for Amiga. * 24.03.97 Windows NT support (Phil Hanna). Clean exit for Amiga WB (Bram) * 02.04.97 Added -E option, to have EBCDIC translation instead of ASCII diff --git a/src/util.h b/src/util.h index 8a300122f0..511c828155 100644 --- a/src/util.h +++ b/src/util.h @@ -168,7 +168,7 @@ inline int64_t abs64(int64_t n) //! implementations. //! //! In particular this class is used for sidestake allocations, both the allocation "percentage", and the CAmount allocations -//! resulting from muliplying the allocation (fraction) times the CAmount rewards. +//! resulting from multiplying the allocation (fraction) times the CAmount rewards. //! class Fraction { public: @@ -356,10 +356,60 @@ class Fraction { return Fraction(overflow_add(slhs.GetNumerator(), srhs.GetNumerator()), slhs.GetDenominator(), true); } - // Otherwise do the full pattern of getting a common denominator and adding, then simplify... - return Fraction(overflow_add(overflow_mult(slhs.GetNumerator(), srhs.GetDenominator()), - overflow_mult(slhs.GetDenominator(), srhs.GetNumerator())), - overflow_mult(slhs.GetDenominator(), srhs.GetDenominator()), + // Now the more complex case. In general, fraction addition follows this pattern: + // + // a c a * (d/g) + c * (b/g) + // - + - , g = gcd(b, d) => --------------------- where {(b/g), (d/g)} will be elements of the counting numbers. + // b d g * (b/g) * (d/g) + // + // (b/g) and (d/g) are divisible with no remainders precisely because of the definition of gcd. + // + // We have already covered the trivial common denominator case above before bothering to compute the gcd of the + // denominator. + int64_t denom_gcd = std::gcd(slhs.GetDenominator(), srhs.GetDenominator()); + + // We have two special cases. One is where g = b (i.e. d is actually a multiple of b). In this case, + // the expression simplifies to + // + // a * (d/b) + c + // ------------- + // d + if (denom_gcd == slhs.GetDenominator()) { + return Fraction(overflow_add(overflow_mult(slhs.GetNumerator(), srhs.GetDenominator() / slhs.GetDenominator()), + srhs.GetNumerator()), + srhs.GetDenominator(), + true); + } + + // The other is where g = d (i.e. b is actually a multiple of d). In this case, + // the expression simplifies to + // + // a + c * (b/d) + // ------------- + // b + if (denom_gcd == srhs.GetDenominator()) { + return Fraction(overflow_add(overflow_mult(srhs.GetNumerator(), slhs.GetDenominator() / srhs.GetDenominator()), + slhs.GetNumerator()), + slhs.GetDenominator(), + true); + } + + // Otherwise do the full pattern of getting a common denominator (pulling out the gcd of the denominators), + // and adding, then simplify... + // + // This approach is more complex than + // + // a * d + c * b + // ------------- + // b * d + // + // but has the advantage of being more resistant to overflows, especially when the two denominators are related by a large + // gcd. In particular in Gridcoin's application with Allocations, the largest denominator of the allocations is 10000, so + // every allocation denominator in reduced form must be divisible evenly into 10000. This means the majority of fraction + // additions will be the two simpler cases above. + return Fraction(overflow_add(overflow_mult(slhs.GetNumerator(), srhs.GetDenominator() / denom_gcd), + overflow_mult(srhs.GetNumerator(), slhs.GetDenominator() / denom_gcd)), + overflow_mult(denom_gcd, overflow_mult(slhs.GetDenominator() / denom_gcd, srhs.GetDenominator() / denom_gcd)), true); } @@ -385,8 +435,36 @@ class Fraction { Fraction slhs(*this, true); Fraction srhs(rhs, true); - return Fraction(overflow_mult(slhs.GetNumerator(), srhs.GetNumerator()), - overflow_mult(slhs.GetDenominator(), srhs.GetDenominator()), + // Gcd's can be used in multiplication for better overflow resistance as well. + // + // Consider + // a c + // - * -, where a/b and c/d are already simplified (i.e. gcd(a, b) = gcd(c, d) = 1. + // b d + // + // We can have g = gcd(a, d) and h = gcd(c, b), which is with the numerators reversed, since multiplication is + // commutative. This means we have + // + // (c / h) (a / g) + // ------- * ------- . + // (b / h) (d / g) + // + // If we form Fraction(c, b, true) and Fraction(a, d, true), the simplification will determine and divide the numerator and + // denominator by h and g respectively. + // + // A specific example is instructive. + // + // 1998 1000 999 1000 1000 999 1 1 + // ---- * ---- = ---- * ---- = ---- * --- = - * - + // 2000 999 1000 999 1000 999 1 1 + // + // This is a formal form of what grade school teachers called factor cancellation. :). + + Fraction sxlhs(srhs.GetNumerator(), slhs.GetDenominator(), true); + Fraction sxrhs(slhs.GetNumerator(), srhs.GetDenominator(), true); + + return Fraction(overflow_mult(sxlhs.GetNumerator(), sxrhs.GetNumerator()), + overflow_mult(sxlhs.GetDenominator(), sxrhs.GetDenominator()), true); } diff --git a/src/validation.cpp b/src/validation.cpp index 53268c936a..f60a215ec1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -891,8 +891,8 @@ class ClaimValidator if (!mandatory_sidestake.empty()) { CAmount actual_output = coinstake.vout[i].nValue; - CAmount required_output = static_cast(mandatory_sidestake[0]->GetAllocation() - * total_owed_to_staker).ToCAmount(); + CAmount required_output = (mandatory_sidestake[0]->GetAllocation() + * total_owed_to_staker).ToCAmount(); if (actual_output >= required_output) { diff --git a/src/wallet/diagnose.h b/src/wallet/diagnose.h index f931fc45e1..606b0205ec 100644 --- a/src/wallet/diagnose.h +++ b/src/wallet/diagnose.h @@ -403,14 +403,16 @@ class CheckClientVersion : public Diagnose m_results_tip_arg.clear(); std::string client_message; + std::string change_log; + GRC::Upgrade::UpgradeType upgrade_type {GRC::Upgrade::UpgradeType::Unknown}; - if (g_UpdateChecker->CheckForLatestUpdate(client_message, false) + if (g_UpdateChecker->CheckForLatestUpdate(client_message, change_log, upgrade_type, false) && client_message.find("mandatory") != std::string::npos) { m_results_tip = _("There is a new mandatory version available and you should upgrade as soon as possible to " "ensure your wallet remains in consensus with the network."); m_results = Diagnose::FAIL; - } else if (g_UpdateChecker->CheckForLatestUpdate(client_message, false) + } else if (g_UpdateChecker->CheckForLatestUpdate(client_message, change_log, upgrade_type, false) && client_message.find("mandatory") == std::string::npos) { m_results_tip = _("There is a new leisure version available and you should upgrade as soon as practical."); m_results = Diagnose::WARNING; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0a9e78857a..17b5b96fe4 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2188,11 +2188,10 @@ UniValue walletpassphrase(const UniValue& params, bool fHelp) strWalletPass.reserve(100); strWalletPass = std::string_view{params[0].get_str()}; - if (strWalletPass.length() > 0) - { + if (strWalletPass.length() > 0) { LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->Unlock(strWalletPass)) + if (!pwalletMain->Unlock(strWalletPass)) { // Check if the passphrase has a null character if (strWalletPass.find('\0') == std::string::npos) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); @@ -2204,11 +2203,12 @@ UniValue walletpassphrase(const UniValue& params, bool fHelp) "the first null character. If this is successful, please set a new " "passphrase to avoid this issue in the future."); } - } - else + } + } else { throw runtime_error( "walletpassphrase \n" "Stores the wallet decryption key in memory for seconds."); + } NewThread(ThreadTopUpKeyPool, nullptr); int64_t* pnSleepTime = new int64_t(nSleepTime); @@ -2252,7 +2252,7 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) + if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { // Check if the old passphrase had a null character if (strOldWalletPass.find('\0') == std::string::npos) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); @@ -2263,12 +2263,13 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp) "please try again with only the characters up to — but not including — " "the first null character."); } + } return NullUniValue; } /** - * Run the walled diagnose checks + * Run the wallet diagnose checks */ UniValue walletdiagnose(const UniValue& params, bool fHelp) { diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 83300f9641..3e35ccf163 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -99,7 +99,8 @@ class CKeyMetadata class CWalletDB : public CDB { public: - CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose) + CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool flush_on_close = true) + : CDB(strFilename, pszMode, flush_on_close) { } private: diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index 9cbee6e709..4529488a77 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -27,3 +27,4 @@ smoe sur clen siz +anull