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..c7eb77127d 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 orginal 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..804c9a6f8a 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 geenral 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) { @@ -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/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/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..adc324b548 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -712,7 +712,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..d318fe8043 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -783,13 +783,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 +829,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/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/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/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()