aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/Makefile.test.include3
-rw-r--r--src/alert.cpp246
-rw-r--r--src/alert.h112
-rw-r--r--src/chainparams.cpp2
-rw-r--r--src/chainparams.h3
-rw-r--r--src/init.cpp3
-rw-r--r--src/net.h18
-rw-r--r--src/net_processing.cpp65
-rw-r--r--src/protocol.cpp2
-rw-r--r--src/protocol.h7
-rw-r--r--src/qt/clientmodel.cpp27
-rw-r--r--src/qt/clientmodel.h2
-rw-r--r--src/test/alert_tests.cpp198
-rw-r--r--src/test/data/alertTests.rawbin0 -> 1281 bytes
-rw-r--r--src/ui_interface.h5
-rw-r--r--src/validation.cpp16
-rw-r--r--src/validation.h3
-rw-r--r--src/warnings.cpp23
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
diff --git a/src/net.h b/src/net.h
index dc727152a..868be7fd9 100644
--- a/src/net.h
+++ b/src/net.h
@@ -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
new file mode 100644
index 000000000..cd3cdbf43
--- /dev/null
+++ b/src/test/data/alertTests.raw
Binary files differ
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")