From 0ccd15354b635e90bb9eb8b0293ca638d55cb54c Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 7 Jan 2024 15:15:22 -0500 Subject: [PATCH 01/14] Make corrections to BeaconRegistry::ActivatePending and Deactivate This commit corrects the algorithms that deal with the expiry of pending beacons during a superblock activation and the inverse, which is the resurrection of pending beacons that were marked expired during the deactivation of a superblock in a chain reorganization scenario. --- src/gridcoin/beacon.cpp | 204 ++++++++++++++++++++++------------------ src/gridcoin/beacon.h | 16 ++++ 2 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index d0e70943bb..5a153107ff 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -880,58 +880,74 @@ 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. + 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 +964,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 +993,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 +1029,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 diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index df941b8ad4..e11f37dd22 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -805,6 +805,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. From 9904ec247b503ade0ae554bbfff058a66daa2cb8 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Thu, 11 Jan 2024 19:26:23 -0500 Subject: [PATCH 02/14] Add circularity detection and triage in beacon chainlet code --- src/gridcoin/beacon.cpp | 84 ++++++++++++++++++++++++++++++++++++++++- src/gridcoin/beacon.h | 13 ++++++- src/gridcoin/tally.cpp | 7 +--- src/rpc/mining.cpp | 44 ++++++--------------- 4 files changed, 107 insertions(+), 41 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index 5a153107ff..7f5482eea9 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -375,8 +375,7 @@ 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() { @@ -788,6 +787,87 @@ int BeaconRegistry::GetDBHeight() return height; } +Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, + std::shared_ptr>> beacon_chain_out) +{ + // Given that we have had rare situations where somehow cirularity 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 later beacon this vector is used to detect the circularity. + std::vector encountered_hashes { beacon->m_hash }; + + Cpid cpid = beacon->m_cpid; + + if (beacon_chain_out != nullptr) { + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: active beacon: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s", + __func__, + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex()); + + beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + } + + // 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. + unsigned int i = 0; + + while (beacon->Renewed()) + { + uint256 current_hash = beacon->m_hash; + + beacon = m_beacon_db.find(beacon->m_previous_hash)->second; + + if (beacon_chain_out != nullptr) { + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: renewal %u beacon: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s.", + __func__, + i, + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex()); + + beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + } + + if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_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. + error("%s: Circularity encountered in beacon ownership chain for beacon with CPID %s, starting at hash %s, " + "at %u linked entries back from the start, with offending hash %s.", + __func__, + cpid.ToString(), + current_hash.GetHex(), + i, + beacon->m_hash.GetHex()); + + std::string str_error = strprintf("ERROR %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " + "starting at hash %s, at %u linked entries back from the start, with offending hash %s.\n" + "\n" + "The client cannot continue and the beacon history has been reset and will be rebuilt " + "on the next restart. Please restart Gridcoin.", + __func__, + cpid.ToString(), + encountered_hashes[0].GetHex(), + i, + beacon->m_hash.GetHex()); + + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"A fatal error has occurred and Gridcoin cannot continue. Please restart."}); + } + + encountered_hashes.push_back(beacon->m_hash); + + ++i; + } + + return beacon; +} + bool BeaconRegistry::NeedsIsContractCorrection() { return m_beacon_db.NeedsIsContractCorrection(); diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index e11f37dd22..de57cd0339 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 @@ -768,6 +767,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 diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 2c54e215f4..024745761f 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -1228,15 +1228,12 @@ 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; - } + beacon_ptr = beacons.GetBeaconChainletRoot(beacon); const CBlockIndex* pindex_baseline = GRC::Tally::GetBaseline(); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 69eb2eaae9..e266f624cf 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -300,47 +300,25 @@ 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; + beacon_ptr = beacons.GetBeaconChainletRoot(beacon_ptr, beacon_chain_out_ptr); - 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. From 856822012dc3eba31508a88efc42011233c3d212 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 13 Jan 2024 17:45:38 -0500 Subject: [PATCH 03/14] Update beacon status logging in beacon_tests --- src/test/gridcoin/beacon_tests.cpp | 141 +++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 20 deletions(-) diff --git a/src/test/gridcoin/beacon_tests.cpp b/src/test/gridcoin/beacon_tests.cpp index 39b4feefbc..c1ff4f34f7 100644 --- a/src/test/gridcoin/beacon_tests.cpp +++ b/src/test/gridcoin/beacon_tests.cpp @@ -176,21 +176,100 @@ 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 + << ", hash = " << beacon->m_hash.GetHex() + << ", 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 + << ", hash = " << beacon->m_hash.GetHex() + << ", 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 + << ", hash = " << activated_beacon->m_hash.GetHex() + << ", 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 + << ", hash = " << iter->m_hash.GetHex() + << ", 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; + } + + } } } @@ -200,6 +279,17 @@ class BeaconRegistryTest for (const auto& iter : registry.Beacons()) { m_beacons_init[iter.first] = *iter.second; + + std::cout << "init beacon record: " + << "hash = " << iter.second->m_hash.GetHex() + << ", cpid = " << iter.second->m_cpid.ToString() + << ", public key = " << HexStr(iter.second->m_public_key) + << ", address = " << iter.second->GetAddress().ToString() + << ", timestamp = " << iter.second->m_timestamp + << ", hash = " << iter.second->m_hash.GetHex() + << ", prev beacon hash = " << iter.second->m_previous_hash.GetHex() + << ", status = " << iter.second->StatusToString() + << std::endl; } m_init_number_beacons = m_beacons_init.size(); @@ -241,6 +331,17 @@ class BeaconRegistryTest for (const auto& iter : registry.Beacons()) { m_beacons_reinit[iter.first] = *iter.second; + + std::cout << "reinit beacon record: " + << "hash = " << iter.second->m_hash.GetHex() + << ", cpid = " << iter.second->m_cpid.ToString() + << ", public key = " << HexStr(iter.second->m_public_key) + << ", address = " << iter.second->GetAddress().ToString() + << ", timestamp = " << iter.second->m_timestamp + << ", hash = " << iter.second->m_hash.GetHex() + << ", prev beacon hash = " << iter.second->m_previous_hash.GetHex() + << ", status = " << iter.second->StatusToString() + << std::endl; } m_reinit_number_beacons = m_beacons_reinit.size(); @@ -314,7 +415,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 +449,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 +477,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 +511,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 +541,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 +558,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 +587,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 +613,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 +640,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 +667,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 +704,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 +734,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 +759,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 +789,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; } } From 3e802329f7456d099e93fde60195da1b764027fa Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 13 Jan 2024 19:15:18 -0500 Subject: [PATCH 04/14] Correct BeaconRegistry::FindHistorical --- src/gridcoin/beacon.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index 7f5482eea9..cbdc426302 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -355,9 +355,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 {}; } bool BeaconRegistry::ContainsActive(const Cpid& cpid, const int64_t now) const From 95f8fb47bf96bed60a16880edbaa592433b25430 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 14 Jan 2024 10:47:36 -0500 Subject: [PATCH 05/14] Provide access to historical beacon set from beacon registry --- src/gridcoin/beacon.cpp | 8 +++++++- src/gridcoin/beacon.h | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index cbdc426302..ef29d041bc 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -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); @@ -987,7 +992,8 @@ void BeaconRegistry::ActivatePending( } } - // Activate the pending beacons that are not expired with respect to pending age. + // 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) { Beacon_ptr last_pending_beacon = iter_pair.second; diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index de57cd0339..ccc34e4b50 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -588,6 +588,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. //! From ffacaf05a7e7f2bc8ef4838ba95936643ce64b11 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 14 Jan 2024 15:25:30 -0500 Subject: [PATCH 06/14] Extend beacon registry db template to cover expired entries This also fixes the HandleCurrentHistoricalEntries specialization in the beacon registry to properly deal with expired pending beacons. --- src/gridcoin/beacon.cpp | 19 +++++++++- src/gridcoin/beacon.h | 1 + src/gridcoin/contract/registry_db.h | 12 ++++-- src/gridcoin/project.cpp | 2 +- src/gridcoin/project.h | 6 ++- src/gridcoin/protocol.cpp | 2 +- src/gridcoin/protocol.h | 6 ++- src/gridcoin/scraper/scraper_registry.cpp | 2 +- src/gridcoin/scraper/scraper_registry.h | 6 ++- src/gridcoin/sidestake.cpp | 2 +- src/gridcoin/sidestake.h | 6 ++- src/test/gridcoin/beacon_tests.cpp | 46 ++++++++++------------- 12 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index ef29d041bc..1290f90d52 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -390,6 +390,7 @@ void BeaconRegistry::Reset() { m_beacons.clear(); m_pending.clear(); + m_expired_pending.clear(); m_beacon_db.clear(); } @@ -1182,6 +1183,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, @@ -1216,7 +1218,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, @@ -1253,6 +1255,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 ", " @@ -1268,6 +1279,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()); } @@ -1294,7 +1308,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()); @@ -1306,6 +1320,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 ccc34e4b50..1174db1c1e 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -811,6 +811,7 @@ class BeaconRegistry : public IContractHandler BeaconStatusForStorage, BeaconMap, PendingBeaconMap, + std::set, HistoricalBeaconMap> BeaconDB; private: diff --git a/src/gridcoin/contract/registry_db.h b/src/gridcoin/contract/registry_db.h index 826aa69bdf..dc51dda00b 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) { 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/test/gridcoin/beacon_tests.cpp b/src/test/gridcoin/beacon_tests.cpp index c1ff4f34f7..369eed61c4 100644 --- a/src/test/gridcoin/beacon_tests.cpp +++ b/src/test/gridcoin/beacon_tests.cpp @@ -182,7 +182,6 @@ class BeaconRegistryTest if (beacon != nullptr) { std::cout << "add beacon record: " << "blockheight = " << ctx.m_pindex->nHeight - << ", hash = " << beacon->m_hash.GetHex() << ", cpid = " << beacon->m_cpid.ToString() << ", public key = " << HexStr(beacon->m_public_key) << ", address = " << beacon->GetAddress().ToString() @@ -203,7 +202,6 @@ class BeaconRegistryTest if (beacon != nullptr) { std::cout << "delete beacon record: " << "blockheight = " << ctx.m_pindex->nHeight - << ", hash = " << beacon->m_hash.GetHex() << ", cpid = " << beacon->m_cpid.ToString() << ", public key = " << HexStr(beacon->m_public_key) << ", address = " << beacon->GetAddress().ToString() @@ -242,7 +240,6 @@ class BeaconRegistryTest if (activated_beacon != nullptr) { std::cout << "activated beacon record: " << "blockheight = " << pindex->nHeight - << ", hash = " << activated_beacon->m_hash.GetHex() << ", cpid = " << activated_beacon->m_cpid.ToString() << ", public key = " << HexStr(activated_beacon->m_public_key) << ", address = " << activated_beacon->GetAddress().ToString() @@ -258,7 +255,6 @@ class BeaconRegistryTest if (iter != nullptr) { std::cout << "expired beacon record: " << "blockheight = " << pindex->nHeight - << ", hash = " << iter->m_hash.GetHex() << ", cpid = " << iter->m_cpid.ToString() << ", public key = " << HexStr(iter->m_public_key) << ", address = " << iter->GetAddress().ToString() @@ -279,17 +275,6 @@ class BeaconRegistryTest for (const auto& iter : registry.Beacons()) { m_beacons_init[iter.first] = *iter.second; - - std::cout << "init beacon record: " - << "hash = " << iter.second->m_hash.GetHex() - << ", cpid = " << iter.second->m_cpid.ToString() - << ", public key = " << HexStr(iter.second->m_public_key) - << ", address = " << iter.second->GetAddress().ToString() - << ", timestamp = " << iter.second->m_timestamp - << ", hash = " << iter.second->m_hash.GetHex() - << ", prev beacon hash = " << iter.second->m_previous_hash.GetHex() - << ", status = " << iter.second->StatusToString() - << std::endl; } m_init_number_beacons = m_beacons_init.size(); @@ -314,6 +299,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); } @@ -331,17 +326,6 @@ class BeaconRegistryTest for (const auto& iter : registry.Beacons()) { m_beacons_reinit[iter.first] = *iter.second; - - std::cout << "reinit beacon record: " - << "hash = " << iter.second->m_hash.GetHex() - << ", cpid = " << iter.second->m_cpid.ToString() - << ", public key = " << HexStr(iter.second->m_public_key) - << ", address = " << iter.second->GetAddress().ToString() - << ", timestamp = " << iter.second->m_timestamp - << ", hash = " << iter.second->m_hash.GetHex() - << ", prev beacon hash = " << iter.second->m_previous_hash.GetHex() - << ", status = " << iter.second->StatusToString() - << std::endl; } m_reinit_number_beacons = m_beacons_reinit.size(); @@ -366,6 +350,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); } }; From e014de2c2b50941bbdf61b3713d03b8641e2084a Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 14 Jan 2024 23:42:57 -0500 Subject: [PATCH 07/14] Add expiry check to Beacon::Renewable method The beacon renewable method originally only checked that the minimum amount of time has passed since the advertisement or last renewal. This commit also adds an expiry check, as an expired beacon cannot be successfully renewed. --- src/gridcoin/beacon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index 1290f90d52..e1d8c7b464 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 From fd243a1c7ba307146c67105de36b8ce33dbacd3a Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Thu, 25 Jan 2024 12:28:50 -0500 Subject: [PATCH 08/14] Fix reversed arguments on passivation log entry --- src/gridcoin/contract/registry_db.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gridcoin/contract/registry_db.h b/src/gridcoin/contract/registry_db.h index dc51dda00b..804c9a6f8a 100644 --- a/src/gridcoin/contract/registry_db.h +++ b/src/gridcoin/contract/registry_db.h @@ -330,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; From 2d0a7b8c6060d5688770e9e58e471250057b8220 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 28 Jan 2024 01:14:30 -0500 Subject: [PATCH 09/14] Increment beacon db version to 3. --- src/gridcoin/beacon.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index 1174db1c1e..64970d7bac 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -544,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) { }; From 18ed4f8bd3a0db167519e7fbb282afaf6fb79745 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Mon, 29 Jan 2024 12:35:50 -0500 Subject: [PATCH 10/14] Cleanup for thread safety Compiling with Clang 15 caught an extra unneeded lock on cs_main and also an unnecessary exclusive lock required on IsV13Enabled. The gArgs global methods are thread-safe via the internal cs in ArgsManager. --- src/chainparams.h | 2 +- src/qt/mrcmodel.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) 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/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; From 95e724e8319dcad46c802661fffbdc751a55818c Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Thu, 1 Feb 2024 19:13:28 -0500 Subject: [PATCH 11/14] Unit tests for BeaconRegistry::GetBeaconChainletRoot --- src/test/gridcoin/beacon_tests.cpp | 439 ++++++++++++++++++++++++++++- 1 file changed, 438 insertions(+), 1 deletion(-) diff --git a/src/test/gridcoin/beacon_tests.cpp b/src/test/gridcoin/beacon_tests.cpp index 369eed61c4..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,27 @@ struct TestKey hasher << GRC::BeaconPayload::CURRENT_VERSION << GRC::Beacon(Public()) - << GRC::Cpid::Parse("00010203040506070809101112131415"); + << Cpid(); + + std::vector signature; + CKey private_key = Private(); + + private_key.Sign(hasher.GetHash(), signature); + + 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(); @@ -105,6 +130,7 @@ struct TestKey return signature; } + }; // TestKey @@ -1223,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() From f8e3dab1299f67e4e1007ef8114eb83f99c85e23 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 2 Feb 2024 00:50:36 -0500 Subject: [PATCH 12/14] Corrections to GetBeaconChainletRoot from detailed unit testing --- src/gridcoin/beacon.cpp | 157 +++++++++++++++++++++++++++++++++------- 1 file changed, 129 insertions(+), 28 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index e1d8c7b464..e26ef16d3d 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -366,7 +366,7 @@ const BeaconOption BeaconRegistry::FindHistorical(const uint256& hash) return beacon_iter->second; } - return {}; + return nullptr; } bool BeaconRegistry::ContainsActive(const Cpid& cpid, const int64_t now) const @@ -800,20 +800,54 @@ int BeaconRegistry::GetDBHeight() Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, std::shared_ptr>> beacon_chain_out) { - // Given that we have had rare situations where somehow cirularity has occurred in the beacon chainlet, which either + Cpid cpid = beacon->m_cpid; + + // The chain head itself. + 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. + + error("%s: Beacon chainlet is corrupted at chainhead for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," + "prev_beacon_ctx_hash = %s, status = %s: not found in the registry.", + __func__, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at chainhead for cpid %s: timestamp = %s" + PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: not found " + "in the registry.", + __func__, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); + } + + // 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 later beacon this vector is used to detect the circularity. + // back to a beacon in a circular manner, this vector is used to detect the circularity. std::vector encountered_hashes { beacon->m_hash }; - Cpid cpid = beacon->m_cpid; - if (beacon_chain_out != nullptr) { - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: active beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s", + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: chainlet head beacon for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s, status = %s.", __func__, + beacon->m_cpid.ToString(), beacon->m_timestamp, beacon->m_hash.GetHex(), - beacon->m_previous_hash.GetHex()); + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); } @@ -825,56 +859,123 @@ Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, while (beacon->Renewed()) { - uint256 current_hash = beacon->m_hash; + // Select previous beacon in chainlet + auto beacon_iter = m_beacon_db.find(beacon->m_previous_hash); - beacon = m_beacon_db.find(beacon->m_previous_hash)->second; + if (beacon_iter == m_beacon_db.end()) { + // Linked beacon in chainlet cannot be found. This is fatal. - if (beacon_chain_out != nullptr) { - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: renewal %u beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s.", - __func__, - i, - beacon->m_timestamp, - beacon->m_hash.GetHex(), - beacon->m_previous_hash.GetHex()); + error("%s: Beacon chainlet is corrupted at %u links back for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," + "prev_beacon_ctx_hash = %s, status = %s: prev_beacon not found in the registry.", + __func__, + i + 1, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at %u links back for cpid %s: timestamp = %s" + PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: prev_beacon not found " + "in the registry.", + __func__, + i + 1, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); - beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); } - if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_hash) != encountered_hashes.end()) { + 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. + error("%s: Circularity encountered in beacon ownership chain for beacon with CPID %s, starting at hash %s, " "at %u linked entries back from the start, with offending hash %s.", __func__, cpid.ToString(), - current_hash.GetHex(), - i, - beacon->m_hash.GetHex()); + beacon->m_hash.GetHex(), + i + 1, + beacon->m_previous_hash.GetHex()); - std::string str_error = strprintf("ERROR %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " + std::string str_error = strprintf("ERROR: %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " "starting at hash %s, at %u linked entries back from the start, with offending hash %s.\n" "\n" "The client cannot continue and the beacon history has been reset and will be rebuilt " "on the next restart. Please restart Gridcoin.", __func__, cpid.ToString(), - encountered_hashes[0].GetHex(), - i, - beacon->m_hash.GetHex()); + beacon->m_hash.GetHex(), + i + 1, + beacon->m_previous_hash.GetHex()); Reset(); uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); - throw std::runtime_error(std::string {"A fatal error has occurred and Gridcoin cannot continue. Please restart."}); + throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); } + // Reassign previous beacon to beacon. + beacon = beacon_iter->second; + encountered_hashes.push_back(beacon->m_hash); + if (beacon_chain_out != nullptr) { + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: beacon %u links back for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s, status = %s.", + __func__, + i + 1, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + } + ++i; } + // Check of initial advertised beacon's previous hash. + 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. + + error("%s: Circularity encountered in beacon ownership chain for beacon with CPID %s, starting at hash %s, " + "at %u linked entries back from the start, with offending hash %s.", + __func__, + cpid.ToString(), + beacon->m_hash.GetHex(), + i + 1, + beacon->m_previous_hash.GetHex()); + + std::string str_error = strprintf("ERROR: %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " + "starting at hash %s, at %u linked entries back from the start, with offending hash %s.\n" + "\n" + "The client cannot continue and the beacon history has been reset and will be rebuilt " + "on the next restart. Please restart Gridcoin.", + __func__, + cpid.ToString(), + beacon->m_hash.GetHex(), + i + 1, + beacon->m_previous_hash.GetHex()); + + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); + } + return beacon; } From 6616bcb443ef8f3cac85175698813b17076aabf0 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 3 Feb 2024 11:55:05 -0500 Subject: [PATCH 13/14] Implement lambdas in GetBeaconChainletRoot to improve readability Also make a few other improvements, such as verification that the previous beacon to the original activated beacon has a status of pending. --- src/gridcoin/beacon.cpp | 186 +++++++++++++++------------------------- 1 file changed, 71 insertions(+), 115 deletions(-) diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index e26ef16d3d..c7eb77127d 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -800,127 +800,100 @@ int BeaconRegistry::GetDBHeight() Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, std::shared_ptr>> beacon_chain_out) { - Cpid cpid = beacon->m_cpid; - - // The chain head itself. - 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. - - error("%s: Beacon chainlet is corrupted at chainhead for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," - "prev_beacon_ctx_hash = %s, status = %s: not found in the registry.", + 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()); + beacon->StatusToString(), + error_message); - std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at chainhead for cpid %s: timestamp = %s" - PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: not found " - "in the registry.", + 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()); + 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 Gridcoin cannot continue. Please restart."}); - } - - // 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 { beacon->m_hash }; + throw std::runtime_error(std::string {"The beacon registry is corrupted and will be rebuilt on the next start. " + "Please restart."}); + }; - if (beacon_chain_out != nullptr) { - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: chainlet head beacon for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," + 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. - unsigned int i = 0; - 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()) { - // Linked beacon in chainlet cannot be found. This is fatal. - - error("%s: Beacon chainlet is corrupted at %u links back for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," - "prev_beacon_ctx_hash = %s, status = %s: prev_beacon not found in the registry.", - __func__, - i + 1, - beacon->m_cpid.ToString(), - beacon->m_timestamp, - beacon->m_hash.GetHex(), - beacon->m_previous_hash.GetHex(), - beacon->StatusToString()); - - std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at %u links back for cpid %s: timestamp = %s" - PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: prev_beacon not found " - "in the registry.", - __func__, - i + 1, - beacon->m_cpid.ToString(), - beacon->m_timestamp, - beacon->m_hash.GetHex(), - beacon->m_previous_hash.GetHex(), - beacon->StatusToString()); - - Reset(); - - uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); - - throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); + 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. - - error("%s: Circularity encountered in beacon ownership chain for beacon with CPID %s, starting at hash %s, " - "at %u linked entries back from the start, with offending hash %s.", - __func__, - cpid.ToString(), - beacon->m_hash.GetHex(), - i + 1, - beacon->m_previous_hash.GetHex()); - - std::string str_error = strprintf("ERROR: %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " - "starting at hash %s, at %u linked entries back from the start, with offending hash %s.\n" - "\n" - "The client cannot continue and the beacon history has been reset and will be rebuilt " - "on the next restart. Please restart Gridcoin.", - __func__, - cpid.ToString(), - beacon->m_hash.GetHex(), - i + 1, - beacon->m_previous_hash.GetHex()); - - Reset(); - - uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); - - throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); + ChainletErrorHandle(i, beacon, "circularity encountered"); } // Reassign previous beacon to beacon. @@ -929,51 +902,34 @@ Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, encountered_hashes.push_back(beacon->m_hash); if (beacon_chain_out != nullptr) { - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: beacon %u links back for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s, status = %s.", - __func__, - i + 1, - beacon->m_cpid.ToString(), - beacon->m_timestamp, - beacon->m_hash.GetHex(), - beacon->m_previous_hash.GetHex(), - beacon->StatusToString()); - - beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + ChainletLinkLog(i, beacon); } ++i; } - // Check of initial advertised beacon's previous hash. - 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. + // 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); - error("%s: Circularity encountered in beacon ownership chain for beacon with CPID %s, starting at hash %s, " - "at %u linked entries back from the start, with offending hash %s.", - __func__, - cpid.ToString(), - beacon->m_hash.GetHex(), - i + 1, - beacon->m_previous_hash.GetHex()); - - std::string str_error = strprintf("ERROR: %s: Circularity encountered in beacon ownership chain for beacon with CPID %s, " - "starting at hash %s, at %u linked entries back from the start, with offending hash %s.\n" - "\n" - "The client cannot continue and the beacon history has been reset and will be rebuilt " - "on the next restart. Please restart Gridcoin.", - __func__, - cpid.ToString(), - beacon->m_hash.GetHex(), - i + 1, - beacon->m_previous_hash.GetHex()); + if (beacon_iter == m_beacon_db.end()) { + ChainletErrorHandle(i, beacon, "previous beacon not found in the registry"); + } - Reset(); + // 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"); + } - uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + 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"); + } - throw std::runtime_error(std::string {"The beacon registry is corrupted and Gridcoin cannot continue. Please restart."}); + // Note that we do not actually walk back to the pending beacon. The parameter beacon remains at the activated beacon. } return beacon; From 7cfaa0197662e241d8bb0a8c7c0a6fa0e4f8de7d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 10 Feb 2024 20:13:55 -0500 Subject: [PATCH 14/14] Add try/catch around calls to GetBeaconChainletRoot The catch for std::runtime_exception in the Tally code will cause a std::abort(). The RPC code will throw a RPC_INTERNAL_ERROR. --- src/gridcoin/tally.cpp | 6 +++++- src/rpc/mining.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 024745761f..0f92700364 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -1233,7 +1233,11 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe // 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. - beacon_ptr = beacons.GetBeaconChainletRoot(beacon); + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon); + } catch (std::runtime_error& e) { + std::abort(); + } const CBlockIndex* pindex_baseline = GRC::Tally::GetBaseline(); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index e266f624cf..be24b0b100 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -307,7 +307,11 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) UniValue beacon_chain(UniValue::VARR); - beacon_ptr = beacons.GetBeaconChainletRoot(beacon_ptr, beacon_chain_out_ptr); + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon_ptr, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + throw JSONRPCError(RPC_INTERNAL_ERROR, e.what()); + } for (const auto& iter : *beacon_chain_out_ptr) { UniValue beacon_chain_entry(UniValue::VOBJ);