introduce and use the generalized node::Warnings interface

Instead of having separate warning functions (and globals) for each
different warning that can be raised, encapsulate this logic into
a single class and allow to (un)set any number of warnings.

Introduces behaviour change:
- the `-alertnotify` command is executed for all
  `KernelNotifications::warningSet` calls, which now also covers the
  `LARGE_WORK_INVALID_CHAIN` warning.
- previously, warnings were returned based on a predetermined order,
  e.g. with the "pre-release test build" warning always first. This
  is no longer the case, and Warnings::GetMessages() will return
  messages sorted by the id of the warning.

Removes warnings.cpp from kernel.
This commit is contained in:
stickies-v
2024-05-07 17:05:40 +01:00
parent 20e616f864
commit b071ad9770
15 changed files with 215 additions and 83 deletions

View File

@@ -12,13 +12,12 @@
#include <atomic>
#include <cstdlib>
#include <string>
namespace node {
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message)
{
SetMiscWarning(message);
g_warnings.Set(Warning::FATAL_INTERNAL_ERROR, message);
InitError(_("A fatal internal error occurred, see debug.log for details: ") + message);
exit_status.store(EXIT_FAILURE);
if (shutdown && !(*shutdown)()) {

View File

@@ -93,7 +93,7 @@ public:
explicit NodeImpl(NodeContext& context) { setContext(&context); }
void initLogging() override { InitLogging(args()); }
void initParameterInteraction() override { InitParameterInteraction(args()); }
bilingual_str getWarnings() override { return Join(GetWarnings(), Untranslated("<hr />")); }
bilingual_str getWarnings() override { return Join(node::g_warnings.GetMessages(), Untranslated("<hr />")); }
int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override

View File

@@ -10,6 +10,7 @@
#include <common/args.h>
#include <common/system.h>
#include <kernel/context.h>
#include <kernel/warning.h>
#include <logging.h>
#include <node/abort.h>
#include <node/interface_ui.h>
@@ -46,16 +47,6 @@ static void AlertNotify(const std::string& strMessage)
#endif
}
static void DoWarning(const bilingual_str& warning)
{
static bool fWarned = false;
node::SetMiscWarning(warning);
if (!fWarned) {
AlertNotify(warning.original);
fWarned = true;
}
}
namespace node {
kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state, CBlockIndex& index)
@@ -80,9 +71,16 @@ void KernelNotifications::progress(const bilingual_str& title, int progress_perc
uiInterface.ShowProgress(title.translated, progress_percent, resume_possible);
}
void KernelNotifications::warning(const bilingual_str& warning)
void KernelNotifications::warningSet(kernel::Warning id, const bilingual_str& message)
{
DoWarning(warning);
if (node::g_warnings.Set(id, message)) {
AlertNotify(message.original);
}
}
void KernelNotifications::warningUnset(kernel::Warning id)
{
g_warnings.Unset(id);
}
void KernelNotifications::flushError(const bilingual_str& message)

View File

@@ -15,6 +15,10 @@ class CBlockIndex;
enum class SynchronizationState;
struct bilingual_str;
namespace kernel {
enum class Warning;
} // namespace kernel
namespace util {
class SignalInterrupt;
} // namespace util
@@ -34,7 +38,9 @@ public:
void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override;
void warning(const bilingual_str& warning) override;
void warningSet(kernel::Warning id, const bilingual_str& message) override;
void warningUnset(kernel::Warning id) override;
void flushError(const bilingual_str& message) override;

View File

@@ -49,7 +49,7 @@ bool TimeOffsets::WarnIfOutOfSync() const
// when median == std::numeric_limits<int64_t>::min(), calling std::chrono::abs is UB
auto median{std::max(Median(), std::chrono::seconds(std::numeric_limits<int64_t>::min() + 1))};
if (std::chrono::abs(median) <= WARN_THRESHOLD) {
node::SetMedianTimeOffsetWarning(std::nullopt);
node::g_warnings.Unset(node::Warning::CLOCK_OUT_OF_SYNC);
uiInterface.NotifyAlertChanged();
return false;
}
@@ -63,7 +63,7 @@ bool TimeOffsets::WarnIfOutOfSync() const
"RPC methods to get more info."
), Ticks<std::chrono::minutes>(WARN_THRESHOLD))};
LogWarning("%s\n", msg.original);
node::SetMedianTimeOffsetWarning(msg);
node::g_warnings.Set(node::Warning::CLOCK_OUT_OF_SYNC, msg);
uiInterface.NotifyAlertChanged();
return true;
}

View File

@@ -12,69 +12,53 @@
#include <univalue.h>
#include <util/translation.h>
#include <optional>
#include <utility>
#include <vector>
static GlobalMutex g_warnings_mutex;
static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex);
static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false;
static std::optional<bilingual_str> g_timeoffset_warning GUARDED_BY(g_warnings_mutex){};
namespace node {
void SetMiscWarning(const bilingual_str& warning)
Warnings g_warnings;
Warnings::Warnings()
{
LOCK(g_warnings_mutex);
g_misc_warnings = warning;
}
void SetfLargeWorkInvalidChainFound(bool flag)
{
LOCK(g_warnings_mutex);
fLargeWorkInvalidChainFound = flag;
}
void SetMedianTimeOffsetWarning(std::optional<bilingual_str> warning)
{
LOCK(g_warnings_mutex);
g_timeoffset_warning = warning;
}
std::vector<bilingual_str> GetWarnings()
{
std::vector<bilingual_str> warnings;
LOCK(g_warnings_mutex);
// Pre-release build warning
if (!CLIENT_VERSION_IS_RELEASE) {
warnings.emplace_back(_("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"));
m_warnings.insert(
{Warning::PRE_RELEASE_TEST_BUILD,
_("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications")});
}
}
bool Warnings::Set(warning_type id, bilingual_str message)
{
LOCK(m_mutex);
const auto& [_, inserted]{m_warnings.insert({id, std::move(message)})};
return inserted;
}
// Misc warnings like out of disk space and clock is wrong
if (!g_misc_warnings.empty()) {
warnings.emplace_back(g_misc_warnings);
bool Warnings::Unset(warning_type id)
{
return WITH_LOCK(m_mutex, return m_warnings.erase(id));
}
std::vector<bilingual_str> Warnings::GetMessages() const
{
LOCK(m_mutex);
std::vector<bilingual_str> messages;
messages.reserve(m_warnings.size());
for (const auto& [id, msg] : m_warnings) {
messages.push_back(msg);
}
if (fLargeWorkInvalidChainFound) {
warnings.emplace_back(_("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."));
}
if (g_timeoffset_warning) {
warnings.emplace_back(g_timeoffset_warning.value());
}
return warnings;
return messages;
}
UniValue GetWarningsForRpc(bool use_deprecated)
{
if (use_deprecated) {
const auto all_warnings{GetWarnings()};
const auto all_warnings{g_warnings.GetMessages()};
return all_warnings.empty() ? "" : all_warnings.back().original;
}
UniValue warnings{UniValue::VARR};
for (auto&& warning : GetWarnings()) {
for (auto&& warning : g_warnings.GetMessages()) {
warnings.push_back(std::move(warning.original));
}
return warnings;

View File

@@ -6,26 +6,86 @@
#ifndef BITCOIN_NODE_WARNINGS_H
#define BITCOIN_NODE_WARNINGS_H
#include <optional>
#include <string>
#include <sync.h>
#include <map>
#include <variant>
#include <vector>
class UniValue;
struct bilingual_str;
namespace kernel {
enum class Warning;
} // namespace kernel
namespace node {
void SetMiscWarning(const bilingual_str& warning);
void SetfLargeWorkInvalidChainFound(bool flag);
/** Pass std::nullopt to disable the warning */
void SetMedianTimeOffsetWarning(std::optional<bilingual_str> warning);
/** Return potential problems detected by the node. */
std::vector<bilingual_str> GetWarnings();
enum class Warning {
CLOCK_OUT_OF_SYNC,
PRE_RELEASE_TEST_BUILD,
FATAL_INTERNAL_ERROR,
};
/**
* RPC helper function that wraps GetWarnings. Returns a UniValue::VSTR
* with the latest warning if use_deprecated is set to true, or a
* UniValue::VARR with all warnings otherwise.
* @class Warnings
* @brief Manages warning messages within a node.
*
* The Warnings class provides mechanisms to set, unset, and retrieve
* warning messages.
*
* This class is designed to be non-copyable to ensure warnings
* are managed centrally.
*/
class Warnings
{
typedef std::variant<kernel::Warning, node::Warning> warning_type;
mutable Mutex m_mutex;
std::map<warning_type, bilingual_str> m_warnings GUARDED_BY(m_mutex);
public:
Warnings();
//! A warnings instance should always be passed by reference, never copied.
Warnings(const Warnings&) = delete;
Warnings& operator=(const Warnings&) = delete;
/**
* @brief Set a warning message. If a warning with the specified
* `id` is already active, false is returned and the new
* warning is ignored. If `id` does not yet exist, the
* warning is set, and true is returned.
*
* @param[in] id Unique identifier of the warning.
* @param[in] message Warning message to be shown.
*
* @returns true if the warning was indeed set (i.e. there is no
* active warning with this `id`), otherwise false.
*/
bool Set(warning_type id, bilingual_str message) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/**
* @brief Unset a warning message. If a warning with the specified
* `id` is active, it is unset, and true is returned.
* Otherwise, no warning is unset and false is returned.
*
* @param[in] id Unique identifier of the warning.
*
* @returns true if the warning was indeed unset (i.e. there is an
* active warning with this `id`), otherwise false.
*/
bool Unset(warning_type id) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
/** Return potential problems detected by the node, sorted by the
* warning_type id */
std::vector<bilingual_str> GetMessages() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
};
/**
* RPC helper function that wraps g_warnings.GetMessages().
*
* Returns a UniValue::VSTR with the latest warning if use_deprecated is
* set to true, or a UniValue::VARR with all warnings otherwise.
*/
UniValue GetWarningsForRpc(bool use_deprecated);
extern Warnings g_warnings;
} // namespace node
#endif // BITCOIN_NODE_WARNINGS_H