diff options
| author | Ross Nicoll <[email protected]> | 2018-04-22 17:19:03 +0100 |
|---|---|---|
| committer | Ross Nicoll <[email protected]> | 2018-09-19 22:11:47 +0100 |
| commit | 41c868f47e671dc5009cd47496c39c13ae36306b (patch) | |
| tree | e48b4f07bce4ff5aac8369551032dc46c46a8bf1 | |
| parent | Update or eliminate remaining tests (#1483) (diff) | |
| download | discoin-41c868f47e671dc5009cd47496c39c13ae36306b.tar.xz discoin-41c868f47e671dc5009cd47496c39c13ae36306b.zip | |
Re-introduce alert functionality (#1470)
Re-introduce alert functionality removed from Bitcoin upstream
| -rw-r--r-- | src/Makefile.am | 3 | ||||
| -rw-r--r-- | src/Makefile.test.include | 3 | ||||
| -rw-r--r-- | src/alert.cpp | 246 | ||||
| -rw-r--r-- | src/alert.h | 112 | ||||
| -rw-r--r-- | src/chainparams.cpp | 2 | ||||
| -rw-r--r-- | src/chainparams.h | 3 | ||||
| -rw-r--r-- | src/init.cpp | 3 | ||||
| -rw-r--r-- | src/net.h | 18 | ||||
| -rw-r--r-- | src/net_processing.cpp | 65 | ||||
| -rw-r--r-- | src/protocol.cpp | 2 | ||||
| -rw-r--r-- | src/protocol.h | 7 | ||||
| -rw-r--r-- | src/qt/clientmodel.cpp | 27 | ||||
| -rw-r--r-- | src/qt/clientmodel.h | 2 | ||||
| -rw-r--r-- | src/test/alert_tests.cpp | 198 | ||||
| -rw-r--r-- | src/test/data/alertTests.raw | bin | 0 -> 1281 bytes | |||
| -rw-r--r-- | src/ui_interface.h | 5 | ||||
| -rw-r--r-- | src/validation.cpp | 16 | ||||
| -rw-r--r-- | src/validation.h | 3 | ||||
| -rw-r--r-- | src/warnings.cpp | 23 |
19 files changed, 708 insertions, 30 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 4e25895f7..3d73e6f05 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,7 @@ endif BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ + alert.h \ auxpow.h \ base58.h \ bloom.h \ @@ -182,6 +183,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ addrdb.cpp \ + alert.cpp \ bloom.cpp \ blockencodings.cpp \ chain.cpp \ @@ -304,6 +306,7 @@ libbitcoin_consensus_a_SOURCES = \ libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ + alert.cpp \ amount.cpp \ arith_uint256.cpp \ auxpow.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c0304337a..56c813fe7 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -71,7 +71,7 @@ JSON_TEST_FILES = \ test/data/tx_valid.json \ test/data/sighash.json -RAW_TEST_FILES = +RAW_TEST_FILES = test/data/alertTests.raw GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h) @@ -80,6 +80,7 @@ BITCOIN_TESTS =\ test/arith_uint256_tests.cpp \ test/scriptnum10.h \ test/addrman_tests.cpp \ + test/alert_tests.cpp \ test/amount_tests.cpp \ test/allocator_tests.cpp \ test/auxpow_tests.cpp \ diff --git a/src/alert.cpp b/src/alert.cpp new file mode 100644 index 000000000..21e4887cf --- /dev/null +++ b/src/alert.cpp @@ -0,0 +1,246 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 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 "alert.h" + +#include "clientversion.h" +#include "net.h" +#include "netmessagemaker.h" +#include "pubkey.h" +#include "timedata.h" +#include "ui_interface.h" +#include "util.h" +#include "utilstrencodings.h" + +#include <stdint.h> +#include <algorithm> +#include <map> + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/foreach.hpp> +#include <boost/thread.hpp> + +using namespace std; + +map<uint256, CAlert> mapAlerts; +CCriticalSection cs_mapAlerts; + +void CUnsignedAlert::SetNull() +{ + nVersion = 1; + nRelayUntil = 0; + nExpiration = 0; + nID = 0; + nCancel = 0; + setCancel.clear(); + nMinVer = 0; + nMaxVer = 0; + setSubVer.clear(); + nPriority = 0; + + strComment.clear(); + strStatusBar.clear(); + strReserved.clear(); +} + +std::string CUnsignedAlert::ToString() const +{ + std::string strSetCancel; + BOOST_FOREACH(int n, setCancel) + strSetCancel += strprintf("%d ", n); + std::string strSetSubVer; + BOOST_FOREACH(const std::string& str, setSubVer) + strSetSubVer += "\"" + str + "\" "; + return strprintf( + "CAlert(\n" + " nVersion = %d\n" + " nRelayUntil = %d\n" + " nExpiration = %d\n" + " nID = %d\n" + " nCancel = %d\n" + " setCancel = %s\n" + " nMinVer = %d\n" + " nMaxVer = %d\n" + " setSubVer = %s\n" + " nPriority = %d\n" + " strComment = \"%s\"\n" + " strStatusBar = \"%s\"\n" + ")\n", + nVersion, + nRelayUntil, + nExpiration, + nID, + nCancel, + strSetCancel, + nMinVer, + nMaxVer, + strSetSubVer, + nPriority, + strComment, + strStatusBar); +} + +void CAlert::SetNull() +{ + CUnsignedAlert::SetNull(); + vchMsg.clear(); + vchSig.clear(); +} + +bool CAlert::IsNull() const +{ + return (nExpiration == 0); +} + +uint256 CAlert::GetHash() const +{ + return Hash(this->vchMsg.begin(), this->vchMsg.end()); +} + +bool CAlert::IsInEffect() const +{ + return (GetAdjustedTime() < nExpiration); +} + +bool CAlert::Cancels(const CAlert& alert) const +{ + if (!IsInEffect()) + return false; // this was a no-op before 31403 + return (alert.nID <= nCancel || setCancel.count(alert.nID)); +} + +bool CAlert::AppliesTo(int nVersion, const std::string& strSubVerIn) const +{ + // TODO: rework for client-version-embedded-in-strSubVer ? + return (IsInEffect() && + nMinVer <= nVersion && nVersion <= nMaxVer && + (setSubVer.empty() || setSubVer.count(strSubVerIn))); +} + +bool CAlert::AppliesToMe() const +{ + return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<std::string>())); +} + +bool CAlert::CheckSignature(const std::vector<unsigned char>& alertKey) const +{ + CPubKey key(alertKey); + if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) + return error("CAlert::CheckSignature(): verify signature failed"); + + // Now unserialize the data + CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); + sMsg >> *(CUnsignedAlert*)this; + return true; +} + +CAlert CAlert::getAlertByHash(const uint256 &hash) +{ + CAlert retval; + { + LOCK(cs_mapAlerts); + map<uint256, CAlert>::iterator mi = mapAlerts.find(hash); + if(mi != mapAlerts.end()) + retval = mi->second; + } + return retval; +} + +bool CAlert::ProcessAlert(const std::vector<unsigned char>& alertKey, bool fThread) +{ + if (!CheckSignature(alertKey)) + return false; + if (!IsInEffect()) + return false; + + // alert.nID=max is reserved for if the alert key is + // compromised. It must have a pre-defined message, + // must never expire, must apply to all versions, + // and must cancel all previous + // alerts or it will be ignored (so an attacker can't + // send an "everything is OK, don't panic" version that + // cannot be overridden): + int maxInt = std::numeric_limits<int>::max(); + if (nID == maxInt) + { + if (!( + nExpiration == maxInt && + nCancel == (maxInt-1) && + nMinVer == 0 && + nMaxVer == maxInt && + setSubVer.empty() && + nPriority == maxInt && + strStatusBar == "URGENT: Alert key compromised, upgrade required" + )) + return false; + } + + { + LOCK(cs_mapAlerts); + // Cancel previous alerts + for (map<uint256, CAlert>::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) + { + const CAlert& alert = (*mi).second; + if (Cancels(alert)) + { + LogPrint("alert", "cancelling alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); + mapAlerts.erase(mi++); + } + else if (!alert.IsInEffect()) + { + LogPrint("alert", "expiring alert %d\n", alert.nID); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); + mapAlerts.erase(mi++); + } + else + mi++; + } + + // Check if this alert has been cancelled + BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) + { + const CAlert& alert = item.second; + if (alert.Cancels(*this)) + { + LogPrint("alert", "alert already cancelled by %d\n", alert.nID); + return false; + } + } + + // Add to mapAlerts + mapAlerts.insert(make_pair(GetHash(), *this)); + // Notify UI and -alertnotify if it applies to me + if(AppliesToMe()) + { + uiInterface.NotifyAlertChanged(GetHash(), CT_NEW); + Notify(strStatusBar, fThread); + } + } + + LogPrint("alert", "accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); + return true; +} + +void +CAlert::Notify(const std::string& strMessage, bool fThread) +{ + std::string strCmd = GetArg("-alertnotify", ""); + if (strCmd.empty()) return; + + // Alert text should be plain ascii coming from a trusted source, but to + // be safe we first strip anything not in safeChars, then add single quotes around + // the whole string before passing it to the shell: + std::string singleQuote("'"); + std::string safeStatus = SanitizeString(strMessage); + safeStatus = singleQuote+safeStatus+singleQuote; + boost::replace_all(strCmd, "%s", safeStatus); + + if (fThread) + boost::thread t(runCommand, strCmd); // thread runs free + else + runCommand(strCmd); +} diff --git a/src/alert.h b/src/alert.h new file mode 100644 index 000000000..7963fac11 --- /dev/null +++ b/src/alert.h @@ -0,0 +1,112 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 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_ALERT_H +#define BITCOIN_ALERT_H + +#include "serialize.h" +#include "sync.h" + +#include <map> +#include <set> +#include <stdint.h> +#include <string> + +class CAlert; +class CNode; +class uint256; + +extern std::map<uint256, CAlert> mapAlerts; +extern CCriticalSection cs_mapAlerts; + +/** Alerts are for notifying old versions if they become too obsolete and + * need to upgrade. The message is displayed in the status bar. + * Alert messages are broadcast as a vector of signed data. Unserializing may + * not read the entire buffer if the alert is for a newer version, but older + * versions can still relay the original data. + */ +class CUnsignedAlert +{ +public: + int nVersion; + int64_t nRelayUntil; // when newer nodes stop relaying to newer nodes + int64_t nExpiration; + int nID; + int nCancel; + std::set<int> setCancel; + int nMinVer; // lowest version inclusive + int nMaxVer; // highest version inclusive + std::set<std::string> setSubVer; // empty matches all + int nPriority; + + // Actions + std::string strComment; + std::string strStatusBar; + std::string strReserved; + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nRelayUntil); + READWRITE(nExpiration); + READWRITE(nID); + READWRITE(nCancel); + READWRITE(setCancel); + READWRITE(nMinVer); + READWRITE(nMaxVer); + READWRITE(setSubVer); + READWRITE(nPriority); + + READWRITE(LIMITED_STRING(strComment, 65536)); + READWRITE(LIMITED_STRING(strStatusBar, 256)); + READWRITE(LIMITED_STRING(strReserved, 256)); + } + + void SetNull(); + + std::string ToString() const; +}; + +/** An alert is a combination of a serialized CUnsignedAlert and a signature. */ +class CAlert : public CUnsignedAlert +{ +public: + std::vector<unsigned char> vchMsg; + std::vector<unsigned char> vchSig; + + CAlert() + { + SetNull(); + } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(vchMsg); + READWRITE(vchSig); + } + + void SetNull(); + bool IsNull() const; + uint256 GetHash() const; + bool IsInEffect() const; + bool Cancels(const CAlert& alert) const; + bool AppliesTo(int nVersion, const std::string& strSubVerIn) const; + bool AppliesToMe() const; + bool CheckSignature(const std::vector<unsigned char>& alertKey) const; + bool ProcessAlert(const std::vector<unsigned char>& alertKey, bool fThread = true); + static void Notify(const std::string& strMessage, bool fThread = true); + + /* + * Get copy of (active) alert object by hash. Returns a null alert if it is not found. + */ + static CAlert getAlertByHash(const uint256 &hash); +}; + +#endif // BITCOIN_ALERT_H diff --git a/src/chainparams.cpp b/src/chainparams.cpp index bdd4a7d46..722cf8c0d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -148,6 +148,7 @@ public: pchMessageStart[1] = 0xc0; pchMessageStart[2] = 0xc0; pchMessageStart[3] = 0xc0; + vAlertPubKey = ParseHex("04d4da7a5dae4db797d9b0644d57a5cd50e05a70f36091cd62e2fc41c98ded06340be5a43a35e185690cd9cde5d72da8f6d065b499b06f51dcfba14aad859f443a"); nDefaultPort = 22556; nPruneAfterHeight = 100000; @@ -297,6 +298,7 @@ public: pchMessageStart[1] = 0xc1; pchMessageStart[2] = 0xb7; pchMessageStart[3] = 0xdc; + vAlertPubKey = ParseHex("042756726da3c7ef515d89212ee1705023d14be389e25fe15611585661b9a20021908b2b80a3c7200a0139dd2b26946606aab0eef9aa7689a6dc2c7eee237fa834"); nDefaultPort = 44556; nPruneAfterHeight = 1000; diff --git a/src/chainparams.h b/src/chainparams.h index d19cdcba5..5c9b01ff4 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -61,6 +61,7 @@ public: } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } + const std::vector<unsigned char>& AlertKey() const { return vAlertPubKey; } int GetDefaultPort() const { return nDefaultPort; } const CBlock& GenesisBlock() const { return genesis; } @@ -87,6 +88,8 @@ protected: Consensus::Params consensus; Consensus::Params *pConsensusRoot; // Binary search tree root CMessageHeader::MessageStartChars pchMessageStart; + //! Raw pub key bytes for the broadcast alert signing key. + std::vector<unsigned char> vAlertPubKey; int nDefaultPort; uint64_t nPruneAfterHeight; std::vector<CDNSSeedData> vSeeds; diff --git a/src/init.cpp b/src/init.cpp index 31f3576b6..023f78bc2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -325,6 +325,7 @@ std::string HelpMessage(HelpMessageMode mode) std::string strUsage = HelpMessageGroup(_("Options:")); strUsage += HelpMessageOpt("-?", _("Print this help message and exit")); strUsage += HelpMessageOpt("-version", _("Print version and exit")); + strUsage += HelpMessageOpt("-alerts", strprintf(_("Receive and display P2P network alerts (default: %u)"), DEFAULT_ALERTS)); strUsage += HelpMessageOpt("-alertnotify=<cmd>", _("Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)")); strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash, %i is replaced by block number)")); if (showDebug) @@ -1051,6 +1052,8 @@ bool AppInitParameterInteraction() fAcceptDatacarrier = GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER); nMaxDatacarrierBytes = GetArg("-datacarriersize", nMaxDatacarrierBytes); + fAlerts = GetBoolArg("-alerts", DEFAULT_ALERTS); + // Option to startup with mocktime set (used for regression testing): SetMockTime(GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op @@ -8,6 +8,7 @@ #include "addrdb.h" #include "addrman.h" +#include "alert.h" #include "amount.h" #include "bloom.h" #include "compat.h" @@ -680,6 +681,9 @@ public: CAmount lastSentFeeFilter; int64_t nextSendTimeFeeFilter; + // Alert relay + std::vector<CAlert> vAlertToSend; + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string &addrNameIn = "", bool fInboundIn = false); ~CNode(); @@ -769,6 +773,14 @@ public: } } + void PushAlert(const CAlert& _alert) + { + // don't relay to nodes which haven't sent their version message + if (_alert.IsInEffect() && nVersion != 0) { + vAlertToSend.push_back(_alert); + } + } + void AddInventoryKnown(const CInv& inv) { @@ -790,6 +802,12 @@ public: } } + void PushAlertHash(const uint256 &hash) + { + LOCK(cs_inventory); + vBlockHashesToAnnounce.push_back(hash); + } + void PushBlockHash(const uint256 &hash) { LOCK(cs_inventory); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 706b4c68f..924fc5eb8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -6,6 +6,7 @@ #include "net_processing.h" #include "addrman.h" +#include "alert.h" #include "arith_uint256.h" #include "blockencodings.h" #include "chainparams.h" @@ -1366,13 +1367,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr pfrom->nTimeOffset = nTimeOffset; AddTimeData(pfrom->addr, nTimeOffset); - // If the peer is old enough to have the old alert system, send it the final alert. - /* if (pfrom->nVersion <= 70012) { - // TODO: Replace this with a valid Dogecoin alert - // Disabled meantime as the remote client considers the nonsense alert a hack and drops the connection - CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION); - connman.PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert)); - } */ + // Relay alerts + { + LOCK(cs_mapAlerts); + BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) + pfrom->PushAlert(item.second); + } // Feeler connections exist only to verify if address is online. if (pfrom->fFeeler) { @@ -2523,6 +2523,38 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } } + else if (fAlerts && strCommand == NetMsgType::ALERT) + { + CAlert alert; + vRecv >> alert; + + uint256 alertHash = alert.GetHash(); + if (pfrom->setKnown.count(alertHash) == 0) + { + if (alert.ProcessAlert(chainparams.AlertKey())) + { + // Relay + pfrom->setKnown.insert(alertHash); + { + + connman.ForEachNode([&alert](CNode* pnode) + { + pnode->PushAlert(alert); + }); + } + } + else { + // Small DoS penalty so peers that send us lots of + // duplicate/expired/invalid-signature/whatever alerts + // eventually get banned. + // This isn't a Misbehaving(100) (immediate ban) because the + // peer might be an older or different implementation with + // a different signature key, etc. + Misbehaving(pfrom->GetId(), 10); + } + } + } + else if (strCommand == NetMsgType::FILTERLOAD) { @@ -3267,6 +3299,25 @@ bool SendMessages(CNode* pto, CConnman& connman, const std::atomic<bool>& interr pto->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; } } + + // + // Message: alert + // + BOOST_FOREACH(const CAlert &alert, pto->vAlertToSend) { + // returns true if wasn't already contained in the set + if (pto->setKnown.insert(alert.GetHash()).second) + { + if (alert.AppliesTo(pto->nVersion, pto->strSubVer) || + alert.AppliesToMe() || + GetAdjustedTime() < alert.nRelayUntil) + { + connman.PushMessage(pto, msgMaker.Make(NetMsgType::ALERT, alert)); + return true; + } + } + } + pto->vAlertToSend.clear(); + } return true; } diff --git a/src/protocol.cpp b/src/protocol.cpp index 28d1d0eeb..94bc2a9c8 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -28,6 +28,7 @@ const char *GETADDR="getaddr"; const char *MEMPOOL="mempool"; const char *PING="ping"; const char *PONG="pong"; +const char *ALERT="alert"; const char *NOTFOUND="notfound"; const char *FILTERLOAD="filterload"; const char *FILTERADD="filteradd"; @@ -60,6 +61,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::MEMPOOL, NetMsgType::PING, NetMsgType::PONG, + NetMsgType::ALERT, NetMsgType::NOTFOUND, NetMsgType::FILTERLOAD, NetMsgType::FILTERADD, diff --git a/src/protocol.h b/src/protocol.h index eba39ab1e..f8bf85141 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -161,6 +161,13 @@ extern const char *PING; */ extern const char *PONG; /** + * The alert message warns nodes of problems that may affect them or the rest + * of the network. + * @since protocol version 311. + * @see https://bitcoin.org/en/developer-reference#alert + */ +extern const char *ALERT; +/** * The notfound message is a reply to a getdata message which requested an * object the receiving node does not have available for relay. * @ince protocol version 70001. diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 20d468797..538bd9518 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -9,6 +9,7 @@ #include "guiutil.h" #include "peertablemodel.h" +#include "alert.h" #include "chainparams.h" #include "checkpoints.h" #include "clientversion.h" @@ -163,8 +164,20 @@ void ClientModel::updateNetworkActive(bool networkActive) Q_EMIT networkActiveChanged(networkActive); } -void ClientModel::updateAlert() +void ClientModel::updateAlert(const QString &hash, int status) { + // Show error message notification for new alert + if(status == CT_NEW) + { + uint256 hash_256; + hash_256.SetHex(hash.toStdString()); + CAlert alert = CAlert::getAlertByHash(hash_256); + if(!alert.IsNull()) + { + Q_EMIT message(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), CClientUIInterface::ICON_ERROR); + } + } + Q_EMIT alertsChanged(getStatusBarWarnings()); } @@ -272,10 +285,12 @@ static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkAct Q_ARG(bool, networkActive)); } -static void NotifyAlertChanged(ClientModel *clientmodel) +static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status) { - qDebug() << "NotifyAlertChanged"; - QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); + qDebug() << "NotifyAlertChanged: " + QString::fromStdString(hash.GetHex()) + " status=" + QString::number(status); + QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); } static void BannedListChanged(ClientModel *clientmodel) @@ -318,7 +333,7 @@ void ClientModel::subscribeToCoreSignals() uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyNetworkActiveChanged.connect(boost::bind(NotifyNetworkActiveChanged, this, _1)); - uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this)); + uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); uiInterface.BannedListChanged.connect(boost::bind(BannedListChanged, this)); uiInterface.NotifyBlockTip.connect(boost::bind(BlockTipChanged, this, _1, _2, false)); uiInterface.NotifyHeaderTip.connect(boost::bind(BlockTipChanged, this, _1, _2, true)); @@ -330,7 +345,7 @@ void ClientModel::unsubscribeFromCoreSignals() uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyNetworkActiveChanged.disconnect(boost::bind(NotifyNetworkActiveChanged, this, _1)); - uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this)); + uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); uiInterface.BannedListChanged.disconnect(boost::bind(BannedListChanged, this)); uiInterface.NotifyBlockTip.disconnect(boost::bind(BlockTipChanged, this, _1, _2, false)); uiInterface.NotifyHeaderTip.disconnect(boost::bind(BlockTipChanged, this, _1, _2, true)); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 4c92e2144..afd291e90 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -115,7 +115,7 @@ public Q_SLOTS: void updateTimer(); void updateNumConnections(int numConnections); void updateNetworkActive(bool networkActive); - void updateAlert(); + void updateAlert(const QString &hash, int status); void updateBanlist(); }; diff --git a/src/test/alert_tests.cpp b/src/test/alert_tests.cpp new file mode 100644 index 000000000..376567efb --- /dev/null +++ b/src/test/alert_tests.cpp @@ -0,0 +1,198 @@ +// Copyright (c) 2013-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Unit tests for alert system + +#include "alert.h" +#include "chain.h" +#include "chainparams.h" +#include "clientversion.h" +#include "data/alertTests.raw.h" +#include "serialize.h" +#include "streams.h" +#include "utilstrencodings.h" + +#include "test/testutil.h" +#include "test/test_bitcoin.h" + +#include <fstream> + +#include <boost/filesystem/operations.hpp> +#include <boost/foreach.hpp> +#include <boost/test/unit_test.hpp> + +extern std::map<std::string, std::string> mapArgs; + +#if 0 +// +// alertTests contains 7 alerts, generated with this code: +// (SignAndSave code not shown, alert signing key is secret) +// +{ + CAlert alert; + alert.nRelayUntil = 60; + alert.nExpiration = 24 * 60 * 60; + alert.nID = 1; + alert.nCancel = 0; // cancels previous messages up to this ID number + alert.nMinVer = 0; // These versions are protocol versions + alert.nMaxVer = 999001; + alert.nPriority = 1; + alert.strComment = "Alert comment"; + alert.strStatusBar = "Alert 1"; + + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.insert(std::string("/Satoshi:0.1.0/")); + alert.strStatusBar = "Alert 1 for Satoshi 0.1.0"; + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.insert(std::string("/Satoshi:0.2.0/")); + alert.strStatusBar = "Alert 1 for Satoshi 0.1.0, 0.2.0"; + SignAndSave(alert, "test/alertTests"); + + alert.setSubVer.clear(); + ++alert.nID; + alert.nCancel = 1; + alert.nPriority = 100; + alert.strStatusBar = "Alert 2, cancels 1"; + SignAndSave(alert, "test/alertTests"); + + alert.nExpiration += 60; + ++alert.nID; + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.nMinVer = 11; + alert.nMaxVer = 22; + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.strStatusBar = "Alert 2 for Satoshi 0.1.0"; + alert.setSubVer.insert(std::string("/Satoshi:0.1.0/")); + SignAndSave(alert, "test/alertTests"); + + ++alert.nID; + alert.nMinVer = 0; + alert.nMaxVer = 999999; + alert.strStatusBar = "Evil Alert'; /bin/ls; echo '"; + alert.setSubVer.clear(); + SignAndSave(alert, "test/alertTests"); +} +#endif + +struct ReadAlerts : public TestingSetup +{ + ReadAlerts() + { + std::vector<unsigned char> vch(alertTests, alertTests + sizeof(alertTests)); + CDataStream stream(vch, SER_DISK, CLIENT_VERSION); + try { + while (!stream.eof()) + { + CAlert alert; + stream >> alert; + alerts.push_back(alert); + } + } + catch (const std::exception&) { } + } + ~ReadAlerts() { } + + static std::vector<std::string> read_lines(boost::filesystem::path filepath) + { + std::vector<std::string> result; + + std::ifstream f(filepath.string().c_str()); + std::string line; + while (std::getline(f,line)) + result.push_back(line); + + return result; + } + + std::vector<CAlert> alerts; +}; + +BOOST_FIXTURE_TEST_SUITE(Alert_tests, ReadAlerts) + + +BOOST_AUTO_TEST_CASE(AlertApplies) +{ + SetMockTime(11); + const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey(); + + BOOST_FOREACH(const CAlert& alert, alerts) + { + BOOST_CHECK(alert.CheckSignature(alertKey)); + } + + BOOST_CHECK(alerts.size() >= 3); + + // Matches: + BOOST_CHECK(alerts[0].AppliesTo(1, "")); + BOOST_CHECK(alerts[0].AppliesTo(999001, "")); + BOOST_CHECK(alerts[0].AppliesTo(1, "/Satoshi:11.11.11/")); + + BOOST_CHECK(alerts[1].AppliesTo(1, "/Satoshi:0.1.0/")); + BOOST_CHECK(alerts[1].AppliesTo(999001, "/Satoshi:0.1.0/")); + + BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.1.0/")); + BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.2.0/")); + + // Don't match: + BOOST_CHECK(!alerts[0].AppliesTo(-1, "")); + BOOST_CHECK(!alerts[0].AppliesTo(999002, "")); + + BOOST_CHECK(!alerts[1].AppliesTo(1, "")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.1.0")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(-1, "/Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(999002, "/Satoshi:0.1.0/")); + BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.2.0/")); + + BOOST_CHECK(!alerts[2].AppliesTo(1, "/Satoshi:0.3.0/")); + + SetMockTime(0); +} + + +BOOST_AUTO_TEST_CASE(AlertNotify) +{ + SetMockTime(11); + const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey(); + + boost::filesystem::path temp = GetTempPath() / + boost::filesystem::unique_path("alertnotify-%%%%.txt"); + + mapArgs["-alertnotify"] = std::string("echo %s >> ") + temp.string(); + + BOOST_FOREACH(CAlert alert, alerts) + alert.ProcessAlert(alertKey, false); + + std::vector<std::string> r = read_lines(temp); + BOOST_CHECK_EQUAL(r.size(), 4u); + +// Windows built-in echo semantics are different than posixy shells. Quotes and +// whitespace are printed literally. + +#ifndef WIN32 + BOOST_CHECK_EQUAL(r[0], "Alert 1"); + BOOST_CHECK_EQUAL(r[1], "Alert 2, cancels 1"); + BOOST_CHECK_EQUAL(r[2], "Alert 2, cancels 1"); + BOOST_CHECK_EQUAL(r[3], "Evil Alert; /bin/ls; echo "); // single-quotes should be removed +#else + BOOST_CHECK_EQUAL(r[0], "'Alert 1' "); + BOOST_CHECK_EQUAL(r[1], "'Alert 2, cancels 1' "); + BOOST_CHECK_EQUAL(r[2], "'Alert 2, cancels 1' "); + BOOST_CHECK_EQUAL(r[3], "'Evil Alert; /bin/ls; echo ' "); +#endif + boost::filesystem::remove(temp); + + SetMockTime(0); +} + +static bool falseFunc() { return false; } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/alertTests.raw b/src/test/data/alertTests.raw Binary files differnew file mode 100644 index 000000000..cd3cdbf43 --- /dev/null +++ b/src/test/data/alertTests.raw diff --git a/src/ui_interface.h b/src/ui_interface.h index 065d23fbb..dd6c4b543 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -89,9 +89,10 @@ public: boost::signals2::signal<void (bool networkActive)> NotifyNetworkActiveChanged; /** - * Status bar alerts changed. + * New, updated or cancelled alert. + * @note called with lock cs_mapAlerts held. */ - boost::signals2::signal<void ()> NotifyAlertChanged; + boost::signals2::signal<void (const uint256 &hash, ChangeType status)> NotifyAlertChanged; /** A wallet has been loaded. */ boost::signals2::signal<void (CWallet* wallet)> LoadWallet; diff --git a/src/validation.cpp b/src/validation.cpp index c5041cea9..d80a5af91 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5,6 +5,7 @@ #include "validation.h" +#include "alert.h" #include "arith_uint256.h" #include "chainparams.h" #include "checkpoints.h" @@ -75,6 +76,7 @@ bool fCheckBlockIndex = false; bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; size_t nCoinCacheUsage = 5000 * 300; uint64_t nPruneTarget = 0; +bool fAlerts = DEFAULT_ALERTS; int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; bool fEnableReplacement = DEFAULT_ENABLE_REPLACEMENT; @@ -1232,19 +1234,7 @@ CBlockIndex *pindexBestForkTip = NULL, *pindexBestForkBase = NULL; static void AlertNotify(const std::string& strMessage) { - uiInterface.NotifyAlertChanged(); - std::string strCmd = GetArg("-alertnotify", ""); - if (strCmd.empty()) return; - - // Alert text should be plain ascii coming from a trusted source, but to - // be safe we first strip anything not in safeChars, then add single quotes around - // the whole string before passing it to the shell: - std::string singleQuote("'"); - std::string safeStatus = SanitizeString(strMessage); - safeStatus = singleQuote+safeStatus+singleQuote; - boost::replace_all(strCmd, "%s", safeStatus); - - boost::thread t(runCommand, strCmd); // thread runs free + CAlert::Notify(strMessage); } void CheckForkWarningConditions() diff --git a/src/validation.h b/src/validation.h index b77477469..de986e972 100644 --- a/src/validation.h +++ b/src/validation.h @@ -47,6 +47,8 @@ struct ChainTxData; struct PrecomputedTransactionData; struct LockPoints; +/** Default for accepting alerts from the P2P network. */ +static const bool DEFAULT_ALERTS = true; /** Default for DEFAULT_WHITELISTRELAY. */ static const bool DEFAULT_WHITELISTRELAY = true; /** Default for DEFAULT_WHITELISTFORCERELAY. */ @@ -176,6 +178,7 @@ extern size_t nCoinCacheUsage; extern CFeeRate minRelayTxFee; /** Absolute maximum transaction fee (in satoshis) used by wallet and mempool (rejects high fee in sendrawtransaction) */ extern CAmount maxTxFee; +extern bool fAlerts; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ extern int64_t nMaxTipAge; extern bool fEnableReplacement; diff --git a/src/warnings.cpp b/src/warnings.cpp index 2c1b1b0e1..f3d87c510 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -3,11 +3,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "alert.h" #include "sync.h" #include "clientversion.h" +#include "uint256.h" #include "util.h" +#include "utilstrencodings.h" #include "warnings.h" +#include <boost/foreach.hpp> + CCriticalSection cs_warnings; std::string strMiscWarning; bool fLargeWorkForkFound = false; @@ -45,6 +50,7 @@ bool GetfLargeWorkInvalidChainFound() std::string GetWarnings(const std::string& strFor) { + int nPriority = 0; std::string strStatusBar; std::string strRPC; std::string strGUI; @@ -63,21 +69,38 @@ std::string GetWarnings(const std::string& strFor) // Misc warnings like out of disk space and clock is wrong if (strMiscWarning != "") { + nPriority = 1000; strStatusBar = strMiscWarning; strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + strMiscWarning; } if (fLargeWorkForkFound) { + nPriority = 2000; strStatusBar = strRPC = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."; strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."); } else if (fLargeWorkInvalidChainFound) { + nPriority = 2000; strStatusBar = strRPC = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."; strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."); } + // Alerts + { + LOCK(cs_mapAlerts); + BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) + { + const CAlert& alert = item.second; + if (alert.AppliesToMe() && alert.nPriority > nPriority) + { + nPriority = alert.nPriority; + strStatusBar = strGUI = alert.strStatusBar; + } + } + } + if (strFor == "gui") return strGUI; else if (strFor == "statusbar") |