Skip to content

Commit

Permalink
Add circularity detection and triage in beacon chainlet code
Browse files Browse the repository at this point in the history
  • Loading branch information
jamescowens committed Jan 12, 2024
1 parent 436eaa0 commit 39c119b
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 41 deletions.
84 changes: 82 additions & 2 deletions src/gridcoin/beacon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -788,6 +787,87 @@ int BeaconRegistry::GetDBHeight()
return height;
}

Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon,
std::shared_ptr<std::vector<std::pair<uint256, int64_t>>> 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<uint256> 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();
Expand Down
13 changes: 12 additions & 1 deletion src/gridcoin/beacon.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#include "gridcoin/contract/registry_db.h"
#include "gridcoin/cpid.h"
#include "gridcoin/support/enumbytes.h"

#include <memory>
#include <string>
#include <vector>
Expand Down Expand Up @@ -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<std::vector<std::pair<uint256, int64_t>>> beacon_chain_out = nullptr);

//!
//! \brief Returns whether IsContract correction is needed in ReplayContracts during initialization
//! \return
Expand Down
7 changes: 2 additions & 5 deletions src/gridcoin/tally.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
44 changes: 11 additions & 33 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::pair<uint256, int64_t>> beacon_chain_out {};

std::shared_ptr<std::vector<std::pair<uint256, int64_t>>> beacon_chain_out_ptr
= std::make_shared<std::vector<std::pair<uint256, int64_t>>>(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.
Expand Down

0 comments on commit 39c119b

Please sign in to comment.