Merge bitcoin/bitcoin#30058: Encapsulate warnings in generalized node::Warnings and remove globals

260f8da71a refactor: remove warnings globals (stickies-v)
9c4b0b7ce4 node: update uiInterface whenever warnings updated (stickies-v)
b071ad9770 introduce and use the generalized `node::Warnings` interface (stickies-v)
20e616f864 move-only: move warnings from common to node (stickies-v)
bed29c481a refactor: remove unnecessary AppendWarning helper function (stickies-v)

Pull request description:

  This PR:
  - moves warnings from common to the node library and into the node namespace (as suggested in https://github.com/bitcoin/bitcoin/pull/29845#discussion_r1570069541)
  - generalizes the warnings interface to `Warnings::Set()` and `Warnings::Unset()` methods, instead of having a separate function and globals for each warning. As a result, this simplifies the `kernel::Notifications` interface.
  - removes warnings.cpp from the kernel library
  - removes warning globals
  - adds testing for the warning logic

  Behaviour change introduced:
  - the `-alertnotify` command is executed for all `KernelNotifications::warningSet` calls, which now also covers the `LARGE_WORK_INVALID_CHAIN` warning
  - the GUI is updated automatically whenever a warning is (un)set, covering some code paths where it previously wouldn't be, e.g. when `node::AbortNode()` is called, or for the `LARGE_WORK_INVALID_CHAIN` warning

  Some discussion points:
  - ~is `const std::string& id` the best way to refer to warnings? Enums are an obvious alternative, but since we need to define warnings across libraries, strings seem like a straightforward solution.~ _edit: updated approach to use `node::Warning` and `kernel::Warning` enums._

ACKs for top commit:
  achow101:
    ACK 260f8da71a
  ryanofsky:
    Code review ACK 260f8da71a. Only change since last review was rebasing
  TheCharlatan:
    Re-ACK 260f8da71a

Tree-SHA512: a3fcedaee0d3ad64e9c111aeb30665162f98e0e72acd6a70b76ff2ddf4f0a34da4f97ce353c322a1668ca6ee4d8a81cc6e6d170c5bbeb7a43cffdaf66646b588
This commit is contained in:
Ava Chow 2024-06-17 17:09:18 -04:00
commit ddf2ebd465
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
39 changed files with 361 additions and 186 deletions

View file

@ -0,0 +1,7 @@
- When running with -alertnotify, an alert can now be raised multiple
times instead of just once. Previously, it was only raised when unknown
new consensus rules were activated, whereas the scope has now been
increased to include all kernel warnings. Specifically, alerts will now
also be raised when an invalid chain with a large amount of work has
been detected. Additional warnings may be added in the future.
(#30058)

View file

@ -196,6 +196,7 @@ BITCOIN_CORE_H = \
kernel/messagestartchars.h \
kernel/notifications_interface.h \
kernel/validation_cache_sizes.h \
kernel/warning.h \
key.h \
key_io.h \
logging.h \
@ -239,6 +240,7 @@ BITCOIN_CORE_H = \
node/types.h \
node/utxo_snapshot.h \
node/validation_cache_args.h \
node/warnings.h \
noui.h \
outputtype.h \
policy/v3_policy.h \
@ -367,7 +369,6 @@ BITCOIN_CORE_H = \
wallet/wallettool.h \
wallet/walletutil.h \
walletinitinterface.h \
warnings.h \
zmq/zmqabstractnotifier.h \
zmq/zmqnotificationinterface.h \
zmq/zmqpublishnotifier.h \
@ -444,6 +445,7 @@ libbitcoin_node_a_SOURCES = \
node/txreconciliation.cpp \
node/utxo_snapshot.cpp \
node/validation_cache_args.cpp \
node/warnings.cpp \
noui.cpp \
policy/v3_policy.cpp \
policy/fees.cpp \
@ -721,7 +723,6 @@ libbitcoin_common_a_SOURCES = \
script/sign.cpp \
script/signingprovider.cpp \
script/solver.cpp \
warnings.cpp \
$(BITCOIN_CORE_H)
#
@ -996,8 +997,7 @@ libbitcoinkernel_la_SOURCES = \
util/tokenpipe.cpp \
validation.cpp \
validationinterface.cpp \
versionbits.cpp \
warnings.cpp
versionbits.cpp
# Required for obj/build.h to be generated first.
# More details: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html

View file

@ -118,6 +118,7 @@ BITCOIN_TESTS =\
test/net_peer_eviction_tests.cpp \
test/net_tests.cpp \
test/netbase_tests.cpp \
test/node_warnings_tests.cpp \
test/orphanage_tests.cpp \
test/peerman_tests.cpp \
test/pmt_tests.cpp \

View file

@ -16,6 +16,7 @@
#include <kernel/checks.h>
#include <kernel/context.h>
#include <kernel/validation_cache_sizes.h>
#include <kernel/warning.h>
#include <consensus/validation.h>
#include <core_io.h>
@ -28,6 +29,7 @@
#include <util/fs.h>
#include <util/signalinterrupt.h>
#include <util/task_runner.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@ -86,9 +88,13 @@ int main(int argc, char* argv[])
{
std::cout << "Progress: " << title.original << ", " << progress_percent << ", " << resume_possible << std::endl;
}
void warning(const bilingual_str& warning) override
void warningSet(kernel::Warning id, const bilingual_str& message) override
{
std::cout << "Warning: " << warning.original << std::endl;
std::cout << "Warning " << static_cast<int>(id) << " set: " << message.original << std::endl;
}
void warningUnset(kernel::Warning id) override
{
std::cout << "Warning " << static_cast<int>(id) << " unset" << std::endl;
}
void flushError(const bilingual_str& message) override
{

View file

@ -17,6 +17,7 @@
#include <kernel/context.h>
#include <node/context.h>
#include <node/interface_ui.h>
#include <node/warnings.h>
#include <noui.h>
#include <util/check.h>
#include <util/exception.h>
@ -181,6 +182,8 @@ static bool AppInit(NodeContext& node)
return false;
}
node.warnings = std::make_unique<node::Warnings>();
node.kernel = std::make_unique<kernel::Context>();
node.ecc_context = std::make_unique<ECC_Context>();
if (!AppInitSanityChecks(*node.kernel))

View file

@ -17,7 +17,6 @@
#include <util/thread.h>
#include <util/translation.h>
#include <validation.h> // For g_chainman
#include <warnings.h>
#include <string>
#include <utility>
@ -31,7 +30,7 @@ template <typename... Args>
void BaseIndex::FatalErrorf(const char* fmt, const Args&... args)
{
auto message = tfm::format(fmt, args...);
node::AbortNode(m_chain->context()->shutdown, m_chain->context()->exit_status, Untranslated(message));
node::AbortNode(m_chain->context()->shutdown, m_chain->context()->exit_status, Untranslated(message), m_chain->context()->warnings.get());
}
CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash)

View file

@ -1486,7 +1486,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// ********************************************************* Step 7: load block chain
node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status);
node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status, *Assert(node.warnings));
ReadNotificationArgs(args, *node.notifications);
ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
@ -1639,7 +1639,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
assert(!node.peerman);
node.peerman = PeerManager::make(*node.connman, *node.addrman,
node.banman.get(), chainman,
*node.mempool, peerman_opts);
*node.mempool, *node.warnings,
peerman_opts);
validation_signals.RegisterValidationInterface(node.peerman.get());
// ********************************************************* Step 8: start indexers

View file

@ -16,6 +16,8 @@ namespace kernel {
//! Result type for use with std::variant to indicate that an operation should be interrupted.
struct Interrupted{};
enum class Warning;
//! Simple result type for functions that need to propagate an interrupt status and don't have other return values.
using InterruptResult = std::variant<std::monostate, Interrupted>;
@ -38,7 +40,8 @@ public:
[[nodiscard]] virtual InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) { return {}; }
virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {}
virtual void warning(const bilingual_str& warning) {}
virtual void warningSet(Warning id, const bilingual_str& message) {}
virtual void warningUnset(Warning id) {}
//! The flush error notification is sent to notify the user that an error
//! occurred while flushing block data to disk. Kernel code may ignore flush

14
src/kernel/warning.h Normal file
View file

@ -0,0 +1,14 @@
// Copyright (c) 2024-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_KERNEL_WARNING_H
#define BITCOIN_KERNEL_WARNING_H
namespace kernel {
enum class Warning {
UNKNOWN_NEW_RULES_ACTIVATED,
LARGE_WORK_INVALID_CHAIN,
};
} // namespace kernel
#endif // BITCOIN_KERNEL_WARNING_H

View file

@ -25,6 +25,7 @@
#include <node/blockstorage.h>
#include <node/timeoffsets.h>
#include <node/txreconciliation.h>
#include <node/warnings.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
@ -489,7 +490,7 @@ class PeerManagerImpl final : public PeerManager
public:
PeerManagerImpl(CConnman& connman, AddrMan& addrman,
BanMan* banman, ChainstateManager& chainman,
CTxMemPool& pool, Options opts);
CTxMemPool& pool, node::Warnings& warnings, Options opts);
/** Overridden from CValidationInterface. */
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override
@ -790,7 +791,8 @@ private:
/** Next time to check for stale tip */
std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s};
TimeOffsets m_outbound_time_offsets;
node::Warnings& m_warnings;
TimeOffsets m_outbound_time_offsets{m_warnings};
const Options m_opts;
@ -2042,14 +2044,14 @@ std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBl
std::unique_ptr<PeerManager> PeerManager::make(CConnman& connman, AddrMan& addrman,
BanMan* banman, ChainstateManager& chainman,
CTxMemPool& pool, Options opts)
CTxMemPool& pool, node::Warnings& warnings, Options opts)
{
return std::make_unique<PeerManagerImpl>(connman, addrman, banman, chainman, pool, opts);
return std::make_unique<PeerManagerImpl>(connman, addrman, banman, chainman, pool, warnings, opts);
}
PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
BanMan* banman, ChainstateManager& chainman,
CTxMemPool& pool, Options opts)
CTxMemPool& pool, node::Warnings& warnings, Options opts)
: m_rng{opts.deterministic_rng},
m_fee_filter_rounder{CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}, m_rng},
m_chainparams(chainman.GetParams()),
@ -2058,6 +2060,7 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
m_banman(banman),
m_chainman(chainman),
m_mempool(pool),
m_warnings{warnings},
m_opts{opts}
{
// While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.

View file

@ -16,6 +16,10 @@ class CChainParams;
class CTxMemPool;
class ChainstateManager;
namespace node {
class Warnings;
} // namespace node
/** Whether transaction reconciliation protocol should be enabled by default. */
static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
@ -73,7 +77,7 @@ public:
static std::unique_ptr<PeerManager> make(CConnman& connman, AddrMan& addrman,
BanMan* banman, ChainstateManager& chainman,
CTxMemPool& pool, Options opts);
CTxMemPool& pool, node::Warnings& warnings, Options opts);
virtual ~PeerManager() { }
/**

View file

@ -6,19 +6,18 @@
#include <logging.h>
#include <node/interface_ui.h>
#include <node/warnings.h>
#include <util/signalinterrupt.h>
#include <util/translation.h>
#include <warnings.h>
#include <atomic>
#include <cstdlib>
#include <string>
namespace node {
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message)
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings)
{
SetMiscWarning(message);
if (warnings) 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

@ -14,7 +14,8 @@ class SignalInterrupt;
} // namespace util
namespace node {
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message);
class Warnings;
void AbortNode(util::SignalInterrupt* shutdown, std::atomic<int>& exit_status, const bilingual_str& message, node::Warnings* warnings);
} // namespace node
#endif // BITCOIN_NODE_ABORT_H

View file

@ -13,6 +13,7 @@
#include <net_processing.h>
#include <netgroup.h>
#include <node/kernel_notifications.h>
#include <node/warnings.h>
#include <policy/fees.h>
#include <scheduler.h>
#include <txmempool.h>

View file

@ -39,6 +39,7 @@ class SignalInterrupt;
namespace node {
class KernelNotifications;
class Warnings;
//! NodeContext struct containing references to chain state and connection
//! state.
@ -81,6 +82,8 @@ struct NodeContext {
//! Issues calls about blocks and transactions
std::unique_ptr<ValidationSignals> validation_signals;
std::atomic<int> exit_status{EXIT_SUCCESS};
//! Manages all the node warnings
std::unique_ptr<node::Warnings> warnings;
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class

View file

@ -32,6 +32,7 @@
#include <node/mini_miner.h>
#include <node/transaction.h>
#include <node/types.h>
#include <node/warnings.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/policy.h>
@ -53,7 +54,6 @@
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <config/bitcoin-config.h> // IWYU pragma: keep
@ -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(Assert(m_context->warnings)->GetMessages(), Untranslated("<hr />")); }
int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override
@ -101,6 +101,7 @@ public:
if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;
if (!AppInitParameterInteraction(args())) return false;
m_context->warnings = std::make_unique<node::Warnings>();
m_context->kernel = std::make_unique<kernel::Context>();
m_context->ecc_context = std::make_unique<ECC_Context>();
if (!AppInitSanityChecks(*m_context->kernel)) return false;

View file

@ -10,15 +10,16 @@
#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>
#include <node/warnings.h>
#include <util/check.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
#include <warnings.h>
#include <cstdint>
#include <string>
@ -28,7 +29,6 @@ using util::ReplaceAll;
static void AlertNotify(const std::string& strMessage)
{
uiInterface.NotifyAlertChanged();
#if HAVE_SYSTEM
std::string strCmd = gArgs.GetArg("-alertnotify", "");
if (strCmd.empty()) return;
@ -46,16 +46,6 @@ static void AlertNotify(const std::string& strMessage)
#endif
}
static void DoWarning(const bilingual_str& warning)
{
static bool fWarned = false;
SetMiscWarning(warning);
if (!fWarned) {
AlertNotify(warning.original);
fWarned = true;
}
}
namespace node {
kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state, CBlockIndex& index)
@ -80,20 +70,27 @@ 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 (m_warnings.Set(id, message)) {
AlertNotify(message.original);
}
}
void KernelNotifications::warningUnset(kernel::Warning id)
{
m_warnings.Unset(id);
}
void KernelNotifications::flushError(const bilingual_str& message)
{
AbortNode(&m_shutdown, m_exit_status, message);
AbortNode(&m_shutdown, m_exit_status, message, &m_warnings);
}
void KernelNotifications::fatalError(const bilingual_str& message)
{
node::AbortNode(m_shutdown_on_fatal_error ? &m_shutdown : nullptr,
m_exit_status, message);
m_exit_status, message, &m_warnings);
}
void ReadNotificationArgs(const ArgsManager& args, KernelNotifications& notifications)

View file

@ -15,18 +15,24 @@ class CBlockIndex;
enum class SynchronizationState;
struct bilingual_str;
namespace kernel {
enum class Warning;
} // namespace kernel
namespace util {
class SignalInterrupt;
} // namespace util
namespace node {
class Warnings;
static constexpr int DEFAULT_STOPATHEIGHT{0};
class KernelNotifications : public kernel::Notifications
{
public:
KernelNotifications(util::SignalInterrupt& shutdown, std::atomic<int>& exit_status) : m_shutdown(shutdown), m_exit_status{exit_status} {}
KernelNotifications(util::SignalInterrupt& shutdown, std::atomic<int>& exit_status, node::Warnings& warnings)
: m_shutdown(shutdown), m_exit_status{exit_status}, m_warnings{warnings} {}
[[nodiscard]] kernel::InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) override;
@ -34,7 +40,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;
@ -47,6 +55,7 @@ public:
private:
util::SignalInterrupt& m_shutdown;
std::atomic<int>& m_exit_status;
node::Warnings& m_warnings;
};
void ReadNotificationArgs(const ArgsManager& args, KernelNotifications& notifications);

View file

@ -3,13 +3,12 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <logging.h>
#include <node/interface_ui.h>
#include <node/timeoffsets.h>
#include <node/warnings.h>
#include <sync.h>
#include <tinyformat.h>
#include <util/time.h>
#include <util/translation.h>
#include <warnings.h>
#include <algorithm>
#include <chrono>
@ -49,8 +48,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) {
SetMedianTimeOffsetWarning(std::nullopt);
uiInterface.NotifyAlertChanged();
m_warnings.Unset(node::Warning::CLOCK_OUT_OF_SYNC);
return false;
}
@ -63,7 +61,6 @@ bool TimeOffsets::WarnIfOutOfSync() const
"RPC methods to get more info."
), Ticks<std::chrono::minutes>(WARN_THRESHOLD))};
LogWarning("%s\n", msg.original);
SetMedianTimeOffsetWarning(msg);
uiInterface.NotifyAlertChanged();
m_warnings.Set(node::Warning::CLOCK_OUT_OF_SYNC, msg);
return true;
}

View file

@ -11,8 +11,16 @@
#include <cstddef>
#include <deque>
namespace node {
class Warnings;
} // namespace node
class TimeOffsets
{
public:
TimeOffsets(node::Warnings& warnings) : m_warnings{warnings} {}
private:
//! Maximum number of timeoffsets stored.
static constexpr size_t MAX_SIZE{50};
//! Minimum difference between system and network time for a warning to be raised.
@ -23,6 +31,8 @@ class TimeOffsets
* positive offset means our peer's clock is ahead of our local clock. */
std::deque<std::chrono::seconds> m_offsets GUARDED_BY(m_mutex){};
node::Warnings& m_warnings;
public:
/** Add a new time offset sample. */
void Add(std::chrono::seconds offset) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);

68
src/node/warnings.cpp Normal file
View file

@ -0,0 +1,68 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <node/warnings.h>
#include <common/system.h>
#include <node/interface_ui.h>
#include <sync.h>
#include <univalue.h>
#include <util/translation.h>
#include <utility>
#include <vector>
namespace node {
Warnings::Warnings()
{
// Pre-release build warning
if (!CLIENT_VERSION_IS_RELEASE) {
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)})};
if (inserted) uiInterface.NotifyAlertChanged();
return inserted;
}
bool Warnings::Unset(warning_type id)
{
auto success{WITH_LOCK(m_mutex, return m_warnings.erase(id))};
if (success) uiInterface.NotifyAlertChanged();
return success;
}
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);
}
return messages;
}
UniValue GetWarningsForRpc(const Warnings& warnings, bool use_deprecated)
{
if (use_deprecated) {
const auto all_messages{warnings.GetMessages()};
return all_messages.empty() ? "" : all_messages.back().original;
}
UniValue messages{UniValue::VARR};
for (auto&& message : warnings.GetMessages()) {
messages.push_back(std::move(message.original));
}
return messages;
}
} // namespace node

90
src/node/warnings.h Normal file
View file

@ -0,0 +1,90 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_NODE_WARNINGS_H
#define BITCOIN_NODE_WARNINGS_H
#include <sync.h>
#include <util/translation.h>
#include <map>
#include <variant>
#include <vector>
class UniValue;
namespace kernel {
enum class Warning;
} // namespace kernel
namespace node {
enum class Warning {
CLOCK_OUT_OF_SYNC,
PRE_RELEASE_TEST_BUILD,
FATAL_INTERNAL_ERROR,
};
/**
* @class Warnings
* @brief Manages warning messages within a node.
*
* The Warnings class provides mechanisms to set, unset, and retrieve
* warning messages. It updates the GUI when warnings are changed.
*
* 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, the UI is updated, 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, the UI is updated, 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 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(const Warnings& warnings, bool use_deprecated);
} // namespace node
#endif // BITCOIN_NODE_WARNINGS_H

View file

@ -29,6 +29,7 @@
#include <node/context.h>
#include <node/transaction.h>
#include <node/utxo_snapshot.h>
#include <node/warnings.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
@ -1308,7 +1309,8 @@ RPCHelpMan getblockchaininfo()
}
}
obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings")));
NodeContext& node = EnsureAnyNodeContext(request.context);
obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings")));
return obj;
},
};

View file

@ -20,6 +20,7 @@
#include <net.h>
#include <node/context.h>
#include <node/miner.h>
#include <node/warnings.h>
#include <pow.h>
#include <rpc/blockchain.h>
#include <rpc/mining.h>
@ -454,7 +455,7 @@ static RPCHelpMan getmininginfo()
obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request));
obj.pushKV("pooledtx", (uint64_t)mempool.size());
obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings")));
obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings")));
return obj;
},
};

View file

@ -16,6 +16,7 @@
#include <netbase.h>
#include <node/context.h>
#include <node/protocol_version.h>
#include <node/warnings.h>
#include <policy/settings.h>
#include <protocol.h>
#include <rpc/blockchain.h>
@ -715,7 +716,7 @@ static RPCHelpMan getnetworkinfo()
}
}
obj.pushKV("localaddresses", std::move(localAddresses));
obj.pushKV("warnings", GetNodeWarnings(IsDeprecatedRPCEnabled("warnings")));
obj.pushKV("warnings", node::GetWarningsForRpc(*CHECK_NONFATAL(node.warnings), IsDeprecatedRPCEnabled("warnings")));
return obj;
},
};

View file

@ -5,17 +5,17 @@
#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <clientversion.h>
#include <core_io.h>
#include <common/args.h>
#include <common/messages.h>
#include <common/types.h>
#include <consensus/amount.h>
#include <script/interpreter.h>
#include <core_io.h>
#include <key_io.h>
#include <node/types.h>
#include <outputtype.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <script/interpreter.h>
#include <script/signingprovider.h>
#include <script/solver.h>
#include <tinyformat.h>
@ -25,7 +25,6 @@
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
#include <warnings.h>
#include <algorithm>
#include <iterator>
@ -1382,17 +1381,3 @@ void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj)
if (warnings.empty()) return;
obj.pushKV("warnings", BilingualStringsToUniValue(warnings));
}
UniValue GetNodeWarnings(bool use_deprecated)
{
if (use_deprecated) {
const auto all_warnings{GetWarnings()};
return all_warnings.empty() ? "" : all_warnings.back().original;
}
UniValue warnings{UniValue::VARR};
for (auto&& warning : GetWarnings()) {
warnings.push_back(std::move(warning.original));
}
return warnings;
}

View file

@ -503,6 +503,4 @@ private:
void PushWarnings(const UniValue& warnings, UniValue& obj);
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj);
UniValue GetNodeWarnings(bool use_deprecated);
#endif // BITCOIN_RPC_UTIL_H

View file

@ -27,7 +27,7 @@ BOOST_FIXTURE_TEST_SUITE(blockmanager_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
{
const auto params {CreateChainParams(ArgsManager{}, ChainType::MAIN)};
KernelNotifications notifications{*Assert(m_node.shutdown), m_node.exit_status};
KernelNotifications notifications{*Assert(m_node.shutdown), m_node.exit_status, *Assert(m_node.warnings)};
const BlockManager::Options blockman_opts{
.chainparams = *params,
.blocks_dir = m_args.GetBlocksDirPath(),
@ -134,7 +134,7 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
{
KernelNotifications notifications{*Assert(m_node.shutdown), m_node.exit_status};
KernelNotifications notifications{*Assert(m_node.shutdown), m_node.exit_status, *Assert(m_node.warnings)};
node::BlockManager::Options blockman_opts{
.chainparams = Params(),
.blocks_dir = m_args.GetBlocksDirPath(),

View file

@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
{
NodeId id{0};
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;
CConnman::Options options;
@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction)
{
NodeId id{0};
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS};
constexpr int64_t MINIMUM_CONNECT_TIME{30};
@ -300,7 +300,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, {});
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
CNetAddr tor_netaddr;
BOOST_REQUIRE(
@ -402,7 +402,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, {});
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
banman->ClearBanned();
int64_t nStartTime = GetTime();

View file

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/timeoffsets.h>
#include <node/warnings.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/util/setup_common.h>
@ -19,7 +20,8 @@ void initialize_timeoffsets()
FUZZ_TARGET(timeoffsets, .init = initialize_timeoffsets)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
TimeOffsets offsets{};
node::Warnings warnings{};
TimeOffsets offsets{warnings};
LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 4'000) {
(void)offsets.Median();
offsets.Add(std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<std::chrono::seconds::rep>()});

View file

@ -84,7 +84,7 @@ static void AddPeer(NodeId& id, std::vector<CNode*>& nodes, PeerManager& peerman
BOOST_AUTO_TEST_CASE(test_addnode_getaddednodeinfo_and_connection_detection)
{
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
auto peerman = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
auto peerman = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
NodeId id{0};
std::vector<CNode*> nodes;

View file

@ -0,0 +1,52 @@
// Copyright (c) 2024-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <node/warnings.h>
#include <util/translation.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(node_warnings_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(warnings)
{
node::Warnings warnings;
// On pre-release builds, a warning is generated automatically
warnings.Unset(node::Warning::PRE_RELEASE_TEST_BUILD);
// For these tests, we don't care what the exact warnings are, so
// just refer to them as warning_1 and warning_2
const auto warning_1{node::Warning::CLOCK_OUT_OF_SYNC};
const auto warning_2{node::Warning::FATAL_INTERNAL_ERROR};
// Ensure we start without any warnings
BOOST_CHECK(warnings.GetMessages().size() == 0);
// Add two warnings
BOOST_CHECK(warnings.Set(warning_1, _("warning 1")));
BOOST_CHECK(warnings.Set(warning_2, _("warning 2")));
// Unset the second one
BOOST_CHECK(warnings.Unset(warning_2));
// Since it's already been unset, this should return false
BOOST_CHECK(!warnings.Unset(warning_2));
// We should now be able to set w2 again
BOOST_CHECK(warnings.Set(warning_2, _("warning 2 - revision 1")));
// Setting w2 again should return false since it's already set
BOOST_CHECK(!warnings.Set(warning_2, _("warning 2 - revision 2")));
// Verify messages are correct
const auto messages{warnings.GetMessages()};
BOOST_CHECK(messages.size() == 2);
BOOST_CHECK(messages[0].original == "warning 1");
BOOST_CHECK(messages[1].original == "warning 2 - revision 1");
// Clearing all warnings should also clear all messages
BOOST_CHECK(warnings.Unset(warning_1));
BOOST_CHECK(warnings.Unset(warning_2));
BOOST_CHECK(warnings.GetMessages().size() == 0);
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -31,7 +31,7 @@ static void mineBlock(const node::NodeContext& node, std::chrono::seconds block_
// Verifying when network-limited peer connections are desirable based on the node's proximity to the tip
BOOST_AUTO_TEST_CASE(connections_desirable_service_flags)
{
std::unique_ptr<PeerManager> peerman = PeerManager::make(*m_node.connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
std::unique_ptr<PeerManager> peerman = PeerManager::make(*m_node.connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
auto consensus = m_node.chainman->GetParams().GetConsensus();
// Check we start connecting to full nodes

View file

@ -4,6 +4,7 @@
//
#include <node/timeoffsets.h>
#include <node/warnings.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
@ -24,7 +25,8 @@ BOOST_FIXTURE_TEST_SUITE(timeoffsets_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(timeoffsets)
{
TimeOffsets offsets{};
node::Warnings warnings{};
TimeOffsets offsets{warnings};
BOOST_CHECK(offsets.Median() == 0s);
AddMulti(offsets, {{0s, -1s, -2s, -3s}});
@ -50,7 +52,8 @@ BOOST_AUTO_TEST_CASE(timeoffsets)
static bool IsWarningRaised(const std::vector<std::chrono::seconds>& check_offsets)
{
TimeOffsets offsets{};
node::Warnings warnings{};
TimeOffsets offsets{warnings};
AddMulti(offsets, check_offsets);
return offsets.WarnIfOutOfSync();
}

View file

@ -31,6 +31,7 @@
#include <node/miner.h>
#include <node/peerman_args.h>
#include <node/validation_cache_args.h>
#include <node/warnings.h>
#include <noui.h>
#include <policy/fees.h>
#include <policy/fees_args.h>
@ -182,6 +183,7 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, const std::vecto
InitLogging(*m_node.args);
AppInitParameterInteraction(*m_node.args);
LogInstance().StartLogging();
m_node.warnings = std::make_unique<node::Warnings>();
m_node.kernel = std::make_unique<kernel::Context>();
m_node.ecc_context = std::make_unique<ECC_Context>();
SetupEnvironment();
@ -230,10 +232,11 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
bilingual_str error{};
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node), error);
Assert(error.empty());
m_node.warnings = std::make_unique<node::Warnings>();
m_cache_sizes = CalculateCacheSizes(m_args);
m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status);
m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status, *Assert(m_node.warnings));
const ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
@ -322,7 +325,8 @@ TestingSetup::TestingSetup(
peerman_opts.deterministic_rng = true;
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
m_node.banman.get(), *m_node.chainman,
*m_node.mempool, peerman_opts);
*m_node.mempool, *m_node.warnings,
peerman_opts);
{
CConnman::Options options;

View file

@ -378,7 +378,7 @@ struct SnapshotTestSetup : TestChain100Setup {
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status);
m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status, *Assert(m_node.warnings));
const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(),
.datadir = chainman.m_options.datadir,

View file

@ -27,14 +27,15 @@
#include <kernel/mempool_entry.h>
#include <kernel/messagestartchars.h>
#include <kernel/notifications_interface.h>
#include <kernel/warning.h>
#include <logging.h>
#include <logging/timer.h>
#include <node/blockstorage.h>
#include <node/utxo_snapshot.h>
#include <policy/v3_policy.h>
#include <policy/policy.h>
#include <policy/rbf.h>
#include <policy/settings.h>
#include <policy/v3_policy.h>
#include <pow.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
@ -57,11 +58,11 @@
#include <util/result.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
#include <util/trace.h>
#include <util/translation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <algorithm>
#include <cassert>
@ -1921,9 +1922,11 @@ void Chainstate::CheckForkWarningConditions()
if (m_chainman.m_best_invalid && m_chainman.m_best_invalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) {
LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n", __func__);
SetfLargeWorkInvalidChainFound(true);
m_chainman.GetNotifications().warningSet(
kernel::Warning::LARGE_WORK_INVALID_CHAIN,
_("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."));
} else {
SetfLargeWorkInvalidChainFound(false);
m_chainman.GetNotifications().warningUnset(kernel::Warning::LARGE_WORK_INVALID_CHAIN);
}
}
@ -2847,13 +2850,6 @@ void Chainstate::PruneAndFlush()
}
}
/** Private helper function that concatenates warning messages. */
static void AppendWarning(bilingual_str& res, const bilingual_str& warn)
{
if (!res.empty()) res += Untranslated(", ");
res += warn;
}
static void UpdateTipLog(
const CCoinsViewCache& coins_tip,
const CBlockIndex* tip,
@ -2904,7 +2900,7 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
g_best_block_cv.notify_all();
}
bilingual_str warning_messages;
std::vector<bilingual_str> warning_messages;
if (!m_chainman.IsInitialBlockDownload()) {
const CBlockIndex* pindex = pindexNew;
for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) {
@ -2913,14 +2909,15 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) {
const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit);
if (state == ThresholdState::ACTIVE) {
m_chainman.GetNotifications().warning(warning);
m_chainman.GetNotifications().warningSet(kernel::Warning::UNKNOWN_NEW_RULES_ACTIVATED, warning);
} else {
AppendWarning(warning_messages, warning);
warning_messages.push_back(warning);
}
}
}
}
UpdateTipLog(coins_tip, pindexNew, params, __func__, "", warning_messages.original);
UpdateTipLog(coins_tip, pindexNew, params, __func__, "",
util::Join(warning_messages, Untranslated(", ")).original);
}
/** Disconnect m_chain's tip.

View file

@ -1,65 +0,0 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <warnings.h>
#include <common/system.h>
#include <sync.h>
#include <util/translation.h>
#include <optional>
#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){};
void SetMiscWarning(const bilingual_str& warning)
{
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"));
}
// Misc warnings like out of disk space and clock is wrong
if (!g_misc_warnings.empty()) {
warnings.emplace_back(g_misc_warnings);
}
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;
}

View file

@ -1,22 +0,0 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WARNINGS_H
#define BITCOIN_WARNINGS_H
#include <optional>
#include <string>
#include <vector>
struct bilingual_str;
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();
#endif // BITCOIN_WARNINGS_H