aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/alert.cpp33
-rw-r--r--src/alert.h2
-rw-r--r--src/bitcoinrpc.cpp4
-rw-r--r--src/db.cpp7
-rw-r--r--src/init.cpp5
-rw-r--r--src/irc.cpp403
-rw-r--r--src/irc.h12
-rw-r--r--src/main.cpp290
-rw-r--r--src/makefile.linux-mingw1
-rw-r--r--src/makefile.mingw1
-rw-r--r--src/makefile.osx1
-rw-r--r--src/makefile.unix1
-rw-r--r--src/net.cpp192
-rw-r--r--src/net.h138
-rw-r--r--src/protocol.h3
-rw-r--r--src/qt/askpassphrasedialog.cpp2
-rw-r--r--src/qt/bitcoin.cpp5
-rw-r--r--src/qt/bitcoinamountfield.cpp5
-rw-r--r--src/qt/bitcoingui.cpp305
-rw-r--r--src/qt/bitcoingui.h44
-rw-r--r--src/qt/guiutil.cpp2
-rw-r--r--src/qt/transactionview.cpp8
-rw-r--r--src/qt/transactionview.h1
-rw-r--r--src/qt/walletframe.cpp129
-rw-r--r--src/qt/walletframe.h73
-rw-r--r--src/qt/walletstack.cpp155
-rw-r--r--src/qt/walletstack.h102
-rw-r--r--src/qt/walletview.cpp354
-rw-r--r--src/qt/walletview.h132
-rw-r--r--src/serialize.h8
-rw-r--r--src/test/alert_tests.cpp185
-rw-r--r--src/test/data/alertTestsbin0 -> 1283 bytes
32 files changed, 1716 insertions, 887 deletions
diff --git a/src/alert.cpp b/src/alert.cpp
index 48920629e2..4b029840dd 100644
--- a/src/alert.cpp
+++ b/src/alert.cpp
@@ -2,6 +2,9 @@
// Alert system
//
+#include <algorithm>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/replace.hpp>
#include <boost/foreach.hpp>
#include <map>
@@ -165,7 +168,7 @@ CAlert CAlert::getAlertByHash(const uint256 &hash)
return retval;
}
-bool CAlert::ProcessAlert()
+bool CAlert::ProcessAlert(bool fThread)
{
if (!CheckSignature())
return false;
@@ -229,9 +232,35 @@ bool CAlert::ProcessAlert()
// Add to mapAlerts
mapAlerts.insert(make_pair(GetHash(), *this));
- // Notify UI if it applies to me
+ // Notify UI and -alertnotify if it applies to me
if(AppliesToMe())
+ {
uiInterface.NotifyAlertChanged(GetHash(), CT_NEW);
+ std::string strCmd = GetArg("-alertnotify", "");
+ if (!strCmd.empty())
+ {
+ // 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("'");
+ // safeChars chosen to allow simple messages/URLs/email addresses, but avoid anything
+ // even possibly remotely dangerous like & or >
+ std::string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 .,;_/:?@");
+ std::string safeStatus;
+ for (std::string::size_type i = 0; i < strStatusBar.size(); i++)
+ {
+ if (safeChars.find(strStatusBar[i]) != std::string::npos)
+ safeStatus.push_back(strStatusBar[i]);
+ }
+ safeStatus = singleQuote+safeStatus+singleQuote;
+ boost::replace_all(strCmd, "%s", safeStatus);
+
+ if (fThread)
+ boost::thread t(runCommand, strCmd); // thread runs free
+ else
+ runCommand(strCmd);
+ }
+ }
}
printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe());
diff --git a/src/alert.h b/src/alert.h
index 7949c76972..25e140f573 100644
--- a/src/alert.h
+++ b/src/alert.h
@@ -91,7 +91,7 @@ public:
bool AppliesToMe() const;
bool RelayTo(CNode* pnode) const;
bool CheckSignature() const;
- bool ProcessAlert();
+ bool ProcessAlert(bool fThread = true);
/*
* Get copy of (active) alert object by hash. Returns a null alert if it is not found.
diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp
index 4a6cc42efc..b6d8de4a18 100644
--- a/src/bitcoinrpc.cpp
+++ b/src/bitcoinrpc.cpp
@@ -769,7 +769,9 @@ void ThreadRPCServer2(void* parg)
"rpcpassword=%s\n"
"(you do not need to remember this password)\n"
"The username and password MUST NOT be the same.\n"
- "If the file does not exist, create it with owner-readable-only file permissions.\n"),
+ "If the file does not exist, create it with owner-readable-only file permissions.\n"
+ "It is also recommended to set alertnotify so you are notified of problems;\n"
+ "for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" admin@foo.com\n"),
strWhatAmI.c_str(),
GetConfigFile().string().c_str(),
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()),
diff --git a/src/db.cpp b/src/db.cpp
index 94629f3cad..e51d5d2cba 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -75,11 +75,10 @@ bool CDBEnv::Open(const boost::filesystem::path& path)
if (GetBoolArg("-privdb", true))
nEnvFlags |= DB_PRIVATE;
- unsigned int nDbCache = GetArg("-dbcache", 25);
dbenv.set_lg_dir(pathLogDir.string().c_str());
- dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1);
- dbenv.set_lg_bsize(1048576);
- dbenv.set_lg_max(10485760);
+ dbenv.set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
+ dbenv.set_lg_bsize(0x10000);
+ dbenv.set_lg_max(1048576);
dbenv.set_lk_max_locks(40000);
dbenv.set_lk_max_objects(40000);
dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
diff --git a/src/init.cpp b/src/init.cpp
index 63610b17fb..1bf76631dc 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -262,7 +262,6 @@ std::string HelpMessage()
" -externalip=<ip> " + _("Specify your own public address") + "\n" +
" -onlynet=<net> " + _("Only connect to nodes in network <net> (IPv4, IPv6 or Tor)") + "\n" +
" -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n" +
- " -irc " + _("Find peers using internet relay chat (default: 0)") + "\n" +
" -checkpoints " + _("Only accept block chain matching built-in checkpoints (default: 1)") + "\n" +
" -listen " + _("Accept connections from outside (default: 1 if no -proxy or -connect)") + "\n" +
" -bind=<addr> " + _("Bind to given address and always listen on it. Use [host]:port notation for IPv6") + "\n" +
@@ -301,6 +300,7 @@ std::string HelpMessage()
" -rpcconnect=<ip> " + _("Send commands to node running on <ip> (default: 127.0.0.1)") + "\n" +
" -blocknotify=<cmd> " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" +
" -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" +
+ " -alertnotify=<cmd> " + _("Execute command when a relevant alert is received (%s in cmd is replaced by message)") + "\n" +
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
@@ -452,9 +452,6 @@ bool AppInit2()
// ********************************************************* Step 2: parameter interactions
fTestNet = GetBoolArg("-testnet");
- if (fTestNet) {
- SoftSetBoolArg("-irc", true);
- }
if (mapArgs.count("-bind")) {
// when specifying an explicit binding address, you want to listen on it
diff --git a/src/irc.cpp b/src/irc.cpp
deleted file mode 100644
index e8471a6630..0000000000
--- a/src/irc.cpp
+++ /dev/null
@@ -1,403 +0,0 @@
-// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2012 The Bitcoin developers
-// Distributed under the MIT/X11 software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include "irc.h"
-#include "net.h"
-#include "base58.h"
-
-#include <boost/algorithm/string/predicate.hpp> // for startswith() and endswith()
-
-using namespace std;
-using namespace boost;
-
-int nGotIRCAddresses = 0;
-
-void ThreadIRCSeed2(void* parg);
-
-
-
-
-#pragma pack(push, 1)
-struct ircaddr
-{
- struct in_addr ip;
- short port;
-};
-#pragma pack(pop)
-
-string EncodeAddress(const CService& addr)
-{
- struct ircaddr tmp;
- if (addr.GetInAddr(&tmp.ip))
- {
- tmp.port = htons(addr.GetPort());
-
- vector<unsigned char> vch(UBEGIN(tmp), UEND(tmp));
- return string("u") + EncodeBase58Check(vch);
- }
- return "";
-}
-
-bool DecodeAddress(string str, CService& addr)
-{
- vector<unsigned char> vch;
- if (!DecodeBase58Check(str.substr(1), vch))
- return false;
-
- struct ircaddr tmp;
- if (vch.size() != sizeof(tmp))
- return false;
- memcpy(&tmp, &vch[0], sizeof(tmp));
-
- addr = CService(tmp.ip, ntohs(tmp.port));
- return true;
-}
-
-
-
-
-
-
-static bool Send(SOCKET hSocket, const char* pszSend)
-{
- if (strstr(pszSend, "PONG") != pszSend)
- printf("IRC SENDING: %s\n", pszSend);
- const char* psz = pszSend;
- const char* pszEnd = psz + strlen(psz);
- while (psz < pszEnd)
- {
- int ret = send(hSocket, psz, pszEnd - psz, MSG_NOSIGNAL);
- if (ret < 0)
- return false;
- psz += ret;
- }
- return true;
-}
-
-bool RecvLineIRC(SOCKET hSocket, string& strLine)
-{
- loop
- {
- bool fRet = RecvLine(hSocket, strLine);
- if (fRet)
- {
- if (fShutdown)
- return false;
- vector<string> vWords;
- ParseString(strLine, ' ', vWords);
- if (vWords.size() >= 1 && vWords[0] == "PING")
- {
- strLine[1] = 'O';
- strLine += '\r';
- Send(hSocket, strLine.c_str());
- continue;
- }
- }
- return fRet;
- }
-}
-
-int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL, const char* psz4=NULL)
-{
- loop
- {
- string strLine;
- strLine.reserve(10000);
- if (!RecvLineIRC(hSocket, strLine))
- return 0;
- printf("IRC %s\n", strLine.c_str());
- if (psz1 && strLine.find(psz1) != string::npos)
- return 1;
- if (psz2 && strLine.find(psz2) != string::npos)
- return 2;
- if (psz3 && strLine.find(psz3) != string::npos)
- return 3;
- if (psz4 && strLine.find(psz4) != string::npos)
- return 4;
- }
-}
-
-bool Wait(int nSeconds)
-{
- if (fShutdown)
- return false;
- printf("IRC waiting %d seconds to reconnect\n", nSeconds);
- for (int i = 0; i < nSeconds; i++)
- {
- if (fShutdown)
- return false;
- Sleep(1000);
- }
- return true;
-}
-
-bool RecvCodeLine(SOCKET hSocket, const char* psz1, string& strRet)
-{
- strRet.clear();
- loop
- {
- string strLine;
- if (!RecvLineIRC(hSocket, strLine))
- return false;
-
- vector<string> vWords;
- ParseString(strLine, ' ', vWords);
- if (vWords.size() < 2)
- continue;
-
- if (vWords[1] == psz1)
- {
- printf("IRC %s\n", strLine.c_str());
- strRet = strLine;
- return true;
- }
- }
-}
-
-bool GetIPFromIRC(SOCKET hSocket, string strMyName, CNetAddr& ipRet)
-{
- Send(hSocket, strprintf("USERHOST %s\r", strMyName.c_str()).c_str());
-
- string strLine;
- if (!RecvCodeLine(hSocket, "302", strLine))
- return false;
-
- vector<string> vWords;
- ParseString(strLine, ' ', vWords);
- if (vWords.size() < 4)
- return false;
-
- string str = vWords[3];
- if (str.rfind("@") == string::npos)
- return false;
- string strHost = str.substr(str.rfind("@")+1);
-
- // Hybrid IRC used by lfnet always returns IP when you userhost yourself,
- // but in case another IRC is ever used this should work.
- printf("GetIPFromIRC() got userhost %s\n", strHost.c_str());
- CNetAddr addr(strHost, true);
- if (!addr.IsValid())
- return false;
- ipRet = addr;
-
- return true;
-}
-
-
-
-void ThreadIRCSeed(void* parg)
-{
- // Make this thread recognisable as the IRC seeding thread
- RenameThread("bitcoin-ircseed");
-
- printf("ThreadIRCSeed started\n");
-
- try
- {
- ThreadIRCSeed2(parg);
- }
- catch (std::exception& e) {
- PrintExceptionContinue(&e, "ThreadIRCSeed()");
- } catch (...) {
- PrintExceptionContinue(NULL, "ThreadIRCSeed()");
- }
- printf("ThreadIRCSeed exited\n");
-}
-
-void ThreadIRCSeed2(void* parg)
-{
- // Don't connect to IRC if we won't use IPv4 connections.
- if (IsLimited(NET_IPV4))
- return;
-
- // ... or if we won't make outbound connections and won't accept inbound ones.
- if (mapArgs.count("-connect") && fNoListen)
- return;
-
- // ... or if IRC is not enabled.
- if (!GetBoolArg("-irc", false))
- return;
-
- printf("ThreadIRCSeed trying to connect...\n");
-
- int nErrorWait = 10;
- int nRetryWait = 10;
- int nNameRetry = 0;
-
- while (!fShutdown)
- {
- CService addrConnect("92.243.23.21", 6667); // irc.lfnet.org
-
- CService addrIRC("irc.lfnet.org", 6667, true);
- if (addrIRC.IsValid())
- addrConnect = addrIRC;
-
- SOCKET hSocket;
- if (!ConnectSocket(addrConnect, hSocket))
- {
- printf("IRC connect failed\n");
- nErrorWait = nErrorWait * 11 / 10;
- if (Wait(nErrorWait += 60))
- continue;
- else
- return;
- }
-
- if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname", "ignoring hostname"))
- {
- closesocket(hSocket);
- hSocket = INVALID_SOCKET;
- nErrorWait = nErrorWait * 11 / 10;
- if (Wait(nErrorWait += 60))
- continue;
- else
- return;
- }
-
- CNetAddr addrIPv4("1.2.3.4"); // arbitrary IPv4 address to make GetLocal prefer IPv4 addresses
- CService addrLocal;
- string strMyName;
- // Don't use our IP as our nick if we're not listening
- // or if it keeps failing because the nick is already in use.
- if (!fNoListen && GetLocal(addrLocal, &addrIPv4) && nNameRetry<3)
- strMyName = EncodeAddress(GetLocalAddress(&addrConnect));
- if (strMyName == "")
- strMyName = strprintf("x%"PRI64u"", GetRand(1000000000));
-
- Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
- Send(hSocket, strprintf("USER %s 8 * : %s\r", strMyName.c_str(), strMyName.c_str()).c_str());
-
- int nRet = RecvUntil(hSocket, " 004 ", " 433 ");
- if (nRet != 1)
- {
- closesocket(hSocket);
- hSocket = INVALID_SOCKET;
- if (nRet == 2)
- {
- printf("IRC name already in use\n");
- nNameRetry++;
- Wait(10);
- continue;
- }
- nErrorWait = nErrorWait * 11 / 10;
- if (Wait(nErrorWait += 60))
- continue;
- else
- return;
- }
- nNameRetry = 0;
- Sleep(500);
-
- // Get our external IP from the IRC server and re-nick before joining the channel
- CNetAddr addrFromIRC;
- if (GetIPFromIRC(hSocket, strMyName, addrFromIRC))
- {
- printf("GetIPFromIRC() returned %s\n", addrFromIRC.ToString().c_str());
- // Don't use our IP as our nick if we're not listening
- if (!fNoListen && addrFromIRC.IsRoutable())
- {
- // IRC lets you to re-nick
- AddLocal(addrFromIRC, LOCAL_IRC);
- strMyName = EncodeAddress(GetLocalAddress(&addrConnect));
- Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
- }
- }
-
- if (fTestNet) {
- Send(hSocket, "JOIN #bitcoinTEST3\r");
- Send(hSocket, "WHO #bitcoinTEST3\r");
- } else {
- // randomly join #bitcoin00-#bitcoin99
- int channel_number = GetRandInt(100);
- Send(hSocket, strprintf("JOIN #bitcoin%02d\r", channel_number).c_str());
- Send(hSocket, strprintf("WHO #bitcoin%02d\r", channel_number).c_str());
- }
-
- int64 nStart = GetTime();
- string strLine;
- strLine.reserve(10000);
- while (!fShutdown && RecvLineIRC(hSocket, strLine))
- {
- if (strLine.empty() || strLine.size() > 900 || strLine[0] != ':')
- continue;
-
- vector<string> vWords;
- ParseString(strLine, ' ', vWords);
- if (vWords.size() < 2)
- continue;
-
- std::string strName;
-
- if (vWords[1] == "352" && vWords.size() >= 8)
- {
- // index 7 is limited to 16 characters
- // could get full length name at index 10, but would be different from join messages
- strName = vWords[7].c_str();
- printf("IRC got who\n");
- }
-
- if (vWords[1] == "JOIN" && vWords[0].size() > 1)
- {
- // :username!username@50000007.F000000B.90000002.IP JOIN :#channelname
- strName = vWords[0].substr(1, vWords[0].find('!', 1) - 1);
- printf("IRC got join\n");
- }
-
- if (boost::algorithm::starts_with(strName, "u"))
- {
- CAddress addr;
- if (DecodeAddress(strName, addr))
- {
- addr.nTime = GetAdjustedTime();
- if (addrman.Add(addr, addrConnect, 51 * 60))
- printf("IRC got new address: %s\n", addr.ToString().c_str());
- nGotIRCAddresses++;
- }
- else
- {
- printf("IRC decode failed\n");
- }
- }
- }
- closesocket(hSocket);
- hSocket = INVALID_SOCKET;
-
- if (GetTime() - nStart > 20 * 60)
- {
- nErrorWait /= 3;
- nRetryWait /= 3;
- }
-
- nRetryWait = nRetryWait * 11 / 10;
- if (!Wait(nRetryWait += 60))
- return;
- }
-}
-
-
-
-
-
-
-
-
-
-
-#ifdef TEST
-int main(int argc, char *argv[])
-{
- WSADATA wsadata;
- if (WSAStartup(MAKEWORD(2,2), &wsadata) != NO_ERROR)
- {
- printf("Error at WSAStartup()\n");
- return false;
- }
-
- ThreadIRCSeed(NULL);
-
- WSACleanup();
- return 0;
-}
-#endif
diff --git a/src/irc.h b/src/irc.h
deleted file mode 100644
index 119aeb3fda..0000000000
--- a/src/irc.h
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2012 The Bitcoin developers
-// Distributed under the MIT/X11 software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_IRC_H
-#define BITCOIN_IRC_H
-
-void ThreadIRCSeed(void* parg);
-
-extern int nGotIRCAddresses;
-
-#endif
diff --git a/src/main.cpp b/src/main.cpp
index 22baf0f3eb..b29091b4fe 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3029,6 +3029,115 @@ bool static AlreadyHave(const CInv& inv)
unsigned char pchMessageStart[4] = { 0xf9, 0xbe, 0xb4, 0xd9 };
+void static ProcessGetData(CNode* pfrom)
+{
+ std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
+
+ vector<CInv> vNotFound;
+
+ while (it != pfrom->vRecvGetData.end()) {
+ // Don't bother if send buffer is too full to respond anyway
+ if (pfrom->nSendSize >= SendBufferSize())
+ break;
+
+ const CInv &inv = *it;
+ {
+ if (fShutdown)
+ break;
+ it++;
+
+ if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
+ {
+ // Send block from disk
+ map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash);
+ if (mi != mapBlockIndex.end())
+ {
+ CBlock block;
+ block.ReadFromDisk((*mi).second);
+ if (inv.type == MSG_BLOCK)
+ pfrom->PushMessage("block", block);
+ else // MSG_FILTERED_BLOCK)
+ {
+ LOCK(pfrom->cs_filter);
+ if (pfrom->pfilter)
+ {
+ CMerkleBlock merkleBlock(block, *pfrom->pfilter);
+ pfrom->PushMessage("merkleblock", merkleBlock);
+ // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see
+ // This avoids hurting performance by pointlessly requiring a round-trip
+ // Note that there is currently no way for a node to request any single transactions we didnt send here -
+ // they must either disconnect and retry or request the full block.
+ // Thus, the protocol spec specified allows for us to provide duplicate txn here,
+ // however we MUST always provide at least what the remote peer needs
+ typedef std::pair<unsigned int, uint256> PairType;
+ BOOST_FOREACH(PairType& pair, merkleBlock.vMatchedTxn)
+ if (!pfrom->setInventoryKnown.count(CInv(MSG_TX, pair.second)))
+ pfrom->PushMessage("tx", block.vtx[pair.first]);
+ }
+ // else
+ // no response
+ }
+
+ // Trigger them to send a getblocks request for the next batch of inventory
+ if (inv.hash == pfrom->hashContinue)
+ {
+ // Bypass PushInventory, this must send even if redundant,
+ // and we want it right after the last block so they don't
+ // wait for other stuff first.
+ vector<CInv> vInv;
+ vInv.push_back(CInv(MSG_BLOCK, hashBestChain));
+ pfrom->PushMessage("inv", vInv);
+ pfrom->hashContinue = 0;
+ }
+ }
+ }
+ else if (inv.IsKnownType())
+ {
+ // Send stream from relay memory
+ bool pushed = false;
+ {
+ LOCK(cs_mapRelay);
+ map<CInv, CDataStream>::iterator mi = mapRelay.find(inv);
+ if (mi != mapRelay.end()) {
+ pfrom->PushMessage(inv.GetCommand(), (*mi).second);
+ pushed = true;
+ }
+ }
+ if (!pushed && inv.type == MSG_TX) {
+ LOCK(mempool.cs);
+ if (mempool.exists(inv.hash)) {
+ CTransaction tx = mempool.lookup(inv.hash);
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss.reserve(1000);
+ ss << tx;
+ pfrom->PushMessage("tx", ss);
+ pushed = true;
+ }
+ }
+ if (!pushed) {
+ vNotFound.push_back(inv);
+ }
+ }
+
+ // Track requests for our stuff.
+ Inventory(inv.hash);
+ }
+ }
+
+ pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it);
+
+ if (!vNotFound.empty()) {
+ // Let the peer know that we didn't find what it asked for, so it doesn't
+ // have to wait around forever. Currently only SPV clients actually care
+ // about this message: it's needed when they are recursively walking the
+ // dependencies of relevant unconfirmed transactions. SPV clients want to
+ // do that because they want to know about (and store and rebroadcast and
+ // risk analyze) the dependencies of transactions relevant to them, without
+ // having to download the entire memory pool.
+ pfrom->PushMessage("notfound", vNotFound);
+ }
+}
+
bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
{
RandAddSeedPerfmon();
@@ -3104,7 +3213,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// Change version
pfrom->PushMessage("verack");
- pfrom->vSend.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
+ pfrom->ssSend.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
if (!pfrom->fInbound)
{
@@ -3168,7 +3277,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
else if (strCommand == "verack")
{
- pfrom->vRecv.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
+ pfrom->SetRecvVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
}
@@ -3302,101 +3411,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
if (fDebugNet || (vInv.size() != 1))
printf("received getdata (%"PRIszu" invsz)\n", vInv.size());
- vector<CInv> vNotFound;
- BOOST_FOREACH(const CInv& inv, vInv)
- {
- if (fShutdown)
- return true;
- if (fDebugNet || (vInv.size() == 1))
- printf("received getdata for: %s\n", inv.ToString().c_str());
+ if ((fDebugNet && vInv.size() > 0) || (vInv.size() == 1))
+ printf("received getdata for: %s\n", vInv[0].ToString().c_str());
- if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
- {
- // Send block from disk
- map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash);
- if (mi != mapBlockIndex.end())
- {
- CBlock block;
- block.ReadFromDisk((*mi).second);
- if (inv.type == MSG_BLOCK)
- pfrom->PushMessage("block", block);
- else // MSG_FILTERED_BLOCK)
- {
- LOCK(pfrom->cs_filter);
- if (pfrom->pfilter)
- {
- CMerkleBlock merkleBlock(block, *pfrom->pfilter);
- pfrom->PushMessage("merkleblock", merkleBlock);
- // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see
- // This avoids hurting performance by pointlessly requiring a round-trip
- // Note that there is currently no way for a node to request any single transactions we didnt send here -
- // they must either disconnect and retry or request the full block.
- // Thus, the protocol spec specified allows for us to provide duplicate txn here,
- // however we MUST always provide at least what the remote peer needs
- typedef std::pair<unsigned int, uint256> PairType;
- BOOST_FOREACH(PairType& pair, merkleBlock.vMatchedTxn)
- if (!pfrom->setInventoryKnown.count(CInv(MSG_TX, pair.second)))
- pfrom->PushMessage("tx", block.vtx[pair.first]);
- }
- // else
- // no response
- }
-
- // Trigger them to send a getblocks request for the next batch of inventory
- if (inv.hash == pfrom->hashContinue)
- {
- // Bypass PushInventory, this must send even if redundant,
- // and we want it right after the last block so they don't
- // wait for other stuff first.
- vector<CInv> vInv;
- vInv.push_back(CInv(MSG_BLOCK, hashBestChain));
- pfrom->PushMessage("inv", vInv);
- pfrom->hashContinue = 0;
- }
- }
- }
- else if (inv.IsKnownType())
- {
- // Send stream from relay memory
- bool pushed = false;
- {
- LOCK(cs_mapRelay);
- map<CInv, CDataStream>::iterator mi = mapRelay.find(inv);
- if (mi != mapRelay.end()) {
- pfrom->PushMessage(inv.GetCommand(), (*mi).second);
- pushed = true;
- }
- }
- if (!pushed && inv.type == MSG_TX) {
- LOCK(mempool.cs);
- if (mempool.exists(inv.hash)) {
- CTransaction tx = mempool.lookup(inv.hash);
- CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
- ss.reserve(1000);
- ss << tx;
- pfrom->PushMessage("tx", ss);
- pushed = true;
- }
- }
- if (!pushed) {
- vNotFound.push_back(inv);
- }
- }
-
- // Track requests for our stuff.
- Inventory(inv.hash);
-
- if (!vNotFound.empty()) {
- // Let the peer know that we didn't find what it asked for, so it doesn't
- // have to wait around forever. Currently only SPV clients actually care
- // about this message: it's needed when they are recursively walking the
- // dependencies of relevant unconfirmed transactions. SPV clients want to
- // do that because they want to know about (and store and rebroadcast and
- // risk analyze) the dependencies of transactions relevant to them, without
- // having to download the entire memory pool.
- pfrom->PushMessage("notfound", vNotFound);
- }
- }
+ pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end());
+ ProcessGetData(pfrom);
}
@@ -3705,13 +3724,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
return true;
}
+// requires LOCK(cs_vRecvMsg)
bool ProcessMessages(CNode* pfrom)
{
- CDataStream& vRecv = pfrom->vRecv;
- if (vRecv.empty())
- return true;
//if (fDebug)
- // printf("ProcessMessages(%u bytes)\n", vRecv.size());
+ // printf("ProcessMessages(%zu messages)\n", pfrom->vRecvMsg.size());
//
// Message format
@@ -3721,33 +3738,41 @@ bool ProcessMessages(CNode* pfrom)
// (4) checksum
// (x) data
//
+ bool fOk = true;
- loop
- {
+ if (!pfrom->vRecvGetData.empty())
+ ProcessGetData(pfrom);
+
+ std::deque<CNetMessage>::iterator it = pfrom->vRecvMsg.begin();
+ while (!pfrom->fDisconnect && it != pfrom->vRecvMsg.end()) {
// Don't bother if send buffer is too full to respond anyway
- if (pfrom->vSend.size() >= SendBufferSize())
+ if (pfrom->nSendSize >= SendBufferSize())
+ break;
+
+ // get next message
+ CNetMessage& msg = *it;
+
+ //if (fDebug)
+ // printf("ProcessMessages(message %u msgsz, %zu bytes, complete:%s)\n",
+ // msg.hdr.nMessageSize, msg.vRecv.size(),
+ // msg.complete() ? "Y" : "N");
+
+ // end, if an incomplete message is found
+ if (!msg.complete())
break;
+ // at this point, any failure means we can delete the current message
+ it++;
+
// Scan for message start
- CDataStream::iterator pstart = search(vRecv.begin(), vRecv.end(), BEGIN(pchMessageStart), END(pchMessageStart));
- int nHeaderSize = vRecv.GetSerializeSize(CMessageHeader());
- if (vRecv.end() - pstart < nHeaderSize)
- {
- if ((int)vRecv.size() > nHeaderSize)
- {
- printf("\n\nPROCESSMESSAGE MESSAGESTART NOT FOUND\n\n");
- vRecv.erase(vRecv.begin(), vRecv.end() - nHeaderSize);
- }
+ if (memcmp(msg.hdr.pchMessageStart, pchMessageStart, sizeof(pchMessageStart)) != 0) {
+ printf("\n\nPROCESSMESSAGE: INVALID MESSAGESTART\n\n");
+ fOk = false;
break;
}
- if (pstart - vRecv.begin() > 0)
- printf("\n\nPROCESSMESSAGE SKIPPED %"PRIpdd" BYTES\n\n", pstart - vRecv.begin());
- vRecv.erase(vRecv.begin(), pstart);
// Read header
- vector<char> vHeaderSave(vRecv.begin(), vRecv.begin() + nHeaderSize);
- CMessageHeader hdr;
- vRecv >> hdr;
+ CMessageHeader& hdr = msg.hdr;
if (!hdr.IsValid())
{
printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str());
@@ -3757,19 +3782,9 @@ bool ProcessMessages(CNode* pfrom)
// Message size
unsigned int nMessageSize = hdr.nMessageSize;
- if (nMessageSize > MAX_SIZE)
- {
- printf("ProcessMessages(%s, %u bytes) : nMessageSize > MAX_SIZE\n", strCommand.c_str(), nMessageSize);
- continue;
- }
- if (nMessageSize > vRecv.size())
- {
- // Rewind and wait for rest of message
- vRecv.insert(vRecv.begin(), vHeaderSave.begin(), vHeaderSave.end());
- break;
- }
// Checksum
+ CDataStream& vRecv = msg.vRecv;
uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize);
unsigned int nChecksum = 0;
memcpy(&nChecksum, &hash, sizeof(nChecksum));
@@ -3780,20 +3795,16 @@ bool ProcessMessages(CNode* pfrom)
continue;
}
- // Copy message to its own buffer
- CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion);
- vRecv.ignore(nMessageSize);
-
// Process message
bool fRet = false;
try
{
{
LOCK(cs_main);
- fRet = ProcessMessage(pfrom, strCommand, vMsg);
+ fRet = ProcessMessage(pfrom, strCommand, vRecv);
}
if (fShutdown)
- return true;
+ break;
}
catch (std::ios_base::failure& e)
{
@@ -3822,8 +3833,11 @@ bool ProcessMessages(CNode* pfrom)
printf("ProcessMessage(%s, %u bytes) FAILED\n", strCommand.c_str(), nMessageSize);
}
- vRecv.Compact();
- return true;
+ // In case the connection got shut down, its receive buffer was wiped
+ if (!pfrom->fDisconnect)
+ pfrom->vRecvMsg.erase(pfrom->vRecvMsg.begin(), it);
+
+ return fOk;
}
@@ -3837,7 +3851,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
// Keep-alive ping. We send a nonce of zero because we don't use it anywhere
// right now.
- if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSend.empty()) {
+ if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSendMsg.empty()) {
uint64 nonce = 0;
if (pto->nVersion > BIP0031_VERSION)
pto->PushMessage("ping", nonce);
diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw
index 7509e2798e..694cacd632 100644
--- a/src/makefile.linux-mingw
+++ b/src/makefile.linux-mingw
@@ -71,7 +71,6 @@ OBJS= \
obj/key.o \
obj/db.o \
obj/init.o \
- obj/irc.o \
obj/keystore.o \
obj/main.o \
obj/net.o \
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 2e092ff686..8b5a5dccd9 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -79,7 +79,6 @@ OBJS= \
obj/key.o \
obj/db.o \
obj/init.o \
- obj/irc.o \
obj/keystore.o \
obj/main.o \
obj/net.o \
diff --git a/src/makefile.osx b/src/makefile.osx
index cdee781257..af12731fa4 100644
--- a/src/makefile.osx
+++ b/src/makefile.osx
@@ -80,7 +80,6 @@ OBJS= \
obj/key.o \
obj/db.o \
obj/init.o \
- obj/irc.o \
obj/keystore.o \
obj/main.o \
obj/net.o \
diff --git a/src/makefile.unix b/src/makefile.unix
index ece2f59cf5..4401cf0d56 100644
--- a/src/makefile.unix
+++ b/src/makefile.unix
@@ -111,7 +111,6 @@ OBJS= \
obj/key.o \
obj/db.o \
obj/init.o \
- obj/irc.o \
obj/keystore.o \
obj/main.o \
obj/net.o \
diff --git a/src/net.cpp b/src/net.cpp
index 3406a28b0e..9ee6cb423c 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -3,7 +3,6 @@
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "irc.h"
#include "db.h"
#include "net.h"
#include "init.h"
@@ -347,7 +346,6 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha
return error("GetMyExternalIP() : connection closed");
}
-// We now get our external IP from the IRC server first and only use this as a backup
bool GetMyExternalIP(CNetAddr& ipRet)
{
CService addrConnect;
@@ -538,7 +536,11 @@ void CNode::CloseSocketDisconnect()
printf("disconnecting node %s\n", addrName.c_str());
closesocket(hSocket);
hSocket = INVALID_SOCKET;
- vRecv.clear();
+
+ // in case this fails, we'll empty the recv buffer when the CNode is deleted
+ TRY_LOCK(cs_vRecvMsg, lockRecv);
+ if (lockRecv)
+ vRecvMsg.clear();
}
}
@@ -630,15 +632,128 @@ void CNode::copyStats(CNodeStats &stats)
}
#undef X
+// requires LOCK(cs_vRecvMsg)
+bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes)
+{
+ while (nBytes > 0) {
+
+ // get current incomplete message, or create a new one
+ if (vRecvMsg.empty() ||
+ vRecvMsg.back().complete())
+ vRecvMsg.push_back(CNetMessage(SER_NETWORK, nRecvVersion));
+
+ CNetMessage& msg = vRecvMsg.back();
+
+ // absorb network data
+ int handled;
+ if (!msg.in_data)
+ handled = msg.readHeader(pch, nBytes);
+ else
+ handled = msg.readData(pch, nBytes);
+
+ if (handled < 0)
+ return false;
+
+ pch += handled;
+ nBytes -= handled;
+ }
+
+ return true;
+}
+
+int CNetMessage::readHeader(const char *pch, unsigned int nBytes)
+{
+ // copy data to temporary parsing buffer
+ unsigned int nRemaining = 24 - nHdrPos;
+ unsigned int nCopy = std::min(nRemaining, nBytes);
+
+ memcpy(&hdrbuf[nHdrPos], pch, nCopy);
+ nHdrPos += nCopy;
+
+ // if header incomplete, exit
+ if (nHdrPos < 24)
+ return nCopy;
+ // deserialize to CMessageHeader
+ try {
+ hdrbuf >> hdr;
+ }
+ catch (std::exception &e) {
+ return -1;
+ }
+
+ // reject messages larger than MAX_SIZE
+ if (hdr.nMessageSize > MAX_SIZE)
+ return -1;
+ // switch state to reading message data
+ in_data = true;
+ vRecv.resize(hdr.nMessageSize);
+
+ return nCopy;
+}
+int CNetMessage::readData(const char *pch, unsigned int nBytes)
+{
+ unsigned int nRemaining = hdr.nMessageSize - nDataPos;
+ unsigned int nCopy = std::min(nRemaining, nBytes);
+
+ memcpy(&vRecv[nDataPos], pch, nCopy);
+ nDataPos += nCopy;
+
+ return nCopy;
+}
+
+
+
+// requires LOCK(cs_vSend)
+void SocketSendData(CNode *pnode)
+{
+ std::deque<CSerializeData>::iterator it = pnode->vSendMsg.begin();
+
+ while (it != pnode->vSendMsg.end()) {
+ const CSerializeData &data = *it;
+ assert(data.size() > pnode->nSendOffset);
+ int nBytes = send(pnode->hSocket, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (nBytes > 0) {
+ pnode->nLastSend = GetTime();
+ pnode->nSendOffset += nBytes;
+ if (pnode->nSendOffset == data.size()) {
+ pnode->nSendOffset = 0;
+ pnode->nSendSize -= data.size();
+ it++;
+ } else {
+ // could not send full message; stop sending more
+ break;
+ }
+ } else {
+ if (nBytes < 0) {
+ // error
+ int nErr = WSAGetLastError();
+ if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
+ {
+ printf("socket send error %d\n", nErr);
+ pnode->CloseSocketDisconnect();
+ }
+ }
+ // couldn't send anything at all
+ break;
+ }
+ }
+
+ if (it == pnode->vSendMsg.end()) {
+ assert(pnode->nSendOffset == 0);
+ assert(pnode->nSendSize == 0);
+ }
+ pnode->vSendMsg.erase(pnode->vSendMsg.begin(), it);
+}
+
void ThreadSocketHandler(void* parg)
{
// Make this thread recognisable as the networking thread
@@ -678,7 +793,7 @@ void ThreadSocketHandler2(void* parg)
BOOST_FOREACH(CNode* pnode, vNodesCopy)
{
if (pnode->fDisconnect ||
- (pnode->GetRefCount() <= 0 && pnode->vRecv.empty() && pnode->vSend.empty()))
+ (pnode->GetRefCount() <= 0 && pnode->vRecvMsg.empty() && pnode->nSendSize == 0 && pnode->ssSend.empty()))
{
// remove from vNodes
vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end());
@@ -710,7 +825,7 @@ void ThreadSocketHandler2(void* parg)
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend)
{
- TRY_LOCK(pnode->cs_vRecv, lockRecv);
+ TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv)
{
TRY_LOCK(pnode->cs_inventory, lockInv);
@@ -761,14 +876,18 @@ void ThreadSocketHandler2(void* parg)
{
if (pnode->hSocket == INVALID_SOCKET)
continue;
- FD_SET(pnode->hSocket, &fdsetRecv);
- FD_SET(pnode->hSocket, &fdsetError);
- hSocketMax = max(hSocketMax, pnode->hSocket);
- have_fds = true;
{
TRY_LOCK(pnode->cs_vSend, lockSend);
- if (lockSend && !pnode->vSend.empty())
- FD_SET(pnode->hSocket, &fdsetSend);
+ if (lockSend) {
+ // do not read, if draining write queue
+ if (!pnode->vSendMsg.empty())
+ FD_SET(pnode->hSocket, &fdsetSend);
+ else
+ FD_SET(pnode->hSocket, &fdsetRecv);
+ FD_SET(pnode->hSocket, &fdsetError);
+ hSocketMax = max(hSocketMax, pnode->hSocket);
+ have_fds = true;
+ }
}
}
}
@@ -875,15 +994,12 @@ void ThreadSocketHandler2(void* parg)
continue;
if (FD_ISSET(pnode->hSocket, &fdsetRecv) || FD_ISSET(pnode->hSocket, &fdsetError))
{
- TRY_LOCK(pnode->cs_vRecv, lockRecv);
+ TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv)
{
- CDataStream& vRecv = pnode->vRecv;
- unsigned int nPos = vRecv.size();
-
- if (nPos > ReceiveBufferSize()) {
+ if (pnode->GetTotalRecvSize() > ReceiveFloodSize()) {
if (!pnode->fDisconnect)
- printf("socket recv flood control disconnect (%"PRIszu" bytes)\n", vRecv.size());
+ printf("socket recv flood control disconnect (%u bytes)\n", pnode->GetTotalRecvSize());
pnode->CloseSocketDisconnect();
}
else {
@@ -892,8 +1008,8 @@ void ThreadSocketHandler2(void* parg)
int nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
if (nBytes > 0)
{
- vRecv.resize(nPos + nBytes);
- memcpy(&vRecv[nPos], pchBuf, nBytes);
+ if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
+ pnode->CloseSocketDisconnect();
pnode->nLastRecv = GetTime();
}
else if (nBytes == 0)
@@ -927,34 +1043,13 @@ void ThreadSocketHandler2(void* parg)
{
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend)
- {
- CDataStream& vSend = pnode->vSend;
- if (!vSend.empty())
- {
- int nBytes = send(pnode->hSocket, &vSend[0], vSend.size(), MSG_NOSIGNAL | MSG_DONTWAIT);
- if (nBytes > 0)
- {
- vSend.erase(vSend.begin(), vSend.begin() + nBytes);
- pnode->nLastSend = GetTime();
- }
- else if (nBytes < 0)
- {
- // error
- int nErr = WSAGetLastError();
- if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
- {
- printf("socket send error %d\n", nErr);
- pnode->CloseSocketDisconnect();
- }
- }
- }
- }
+ SocketSendData(pnode);
}
//
// Inactivity checking
//
- if (pnode->vSend.empty())
+ if (pnode->vSendMsg.empty())
pnode->nLastSendEmpty = GetTime();
if (GetTime() - pnode->nTimeConnected > 60)
{
@@ -1155,6 +1250,7 @@ static const char *strMainNetDNSSeed[][2] = {
static const char *strTestNetDNSSeed[][2] = {
{"bitcoin.petertodd.org", "testnet-seed.bitcoin.petertodd.org"},
+ {"bluematt.me", "testnet-seed.bluematt.me"},
{NULL, NULL}
};
@@ -1692,11 +1788,15 @@ void ThreadMessageHandler2(void* parg)
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
BOOST_FOREACH(CNode* pnode, vNodesCopy)
{
+ if (pnode->fDisconnect)
+ continue;
+
// Receive messages
{
- TRY_LOCK(pnode->cs_vRecv, lockRecv);
+ TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv)
- ProcessMessages(pnode);
+ if (!ProcessMessages(pnode))
+ pnode->CloseSocketDisconnect();
}
if (fShutdown)
return;
@@ -1928,10 +2028,6 @@ void StartNode(void* parg)
if (fUseUPnP)
MapPort();
- // Get addresses from IRC and advertise ours
- if (!NewThread(ThreadIRCSeed, NULL))
- printf("Error: NewThread(ThreadIRCSeed) failed\n");
-
// Send and receive from sockets, accept connections
if (!NewThread(ThreadSocketHandler, NULL))
printf("Error: NewThread(ThreadSocketHandler) failed\n");
diff --git a/src/net.h b/src/net.h
index 14e12aa102..368e4cd4bb 100644
--- a/src/net.h
+++ b/src/net.h
@@ -27,7 +27,7 @@ extern int nBestHeight;
-inline unsigned int ReceiveBufferSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); }
+inline unsigned int ReceiveFloodSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); }
inline unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); }
void AddOneShot(std::string strDest);
@@ -42,6 +42,7 @@ unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
void StartNode(void* parg);
bool StopNode();
+void SocketSendData(CNode *pnode);
enum
{
@@ -49,7 +50,6 @@ enum
LOCAL_IF, // address a local interface listens on
LOCAL_BIND, // address explicit bound to
LOCAL_UPNP, // address reported by UPnP
- LOCAL_IRC, // address reported by IRC (deprecated)
LOCAL_HTTP, // address reported by whatismyip.com and similar
LOCAL_MANUAL, // address explicitly specified (-externalip=)
@@ -127,6 +127,44 @@ public:
+class CNetMessage {
+public:
+ bool in_data; // parsing header (false) or data (true)
+
+ CDataStream hdrbuf; // partially received header
+ CMessageHeader hdr; // complete header
+ unsigned int nHdrPos;
+
+ CDataStream vRecv; // received message data
+ unsigned int nDataPos;
+
+ CNetMessage(int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), vRecv(nTypeIn, nVersionIn) {
+ hdrbuf.resize(24);
+ in_data = false;
+ nHdrPos = 0;
+ nDataPos = 0;
+ }
+
+ bool complete() const
+ {
+ if (!in_data)
+ return false;
+ return (hdr.nMessageSize == nDataPos);
+ }
+
+ void SetVersion(int nVersionIn)
+ {
+ hdrbuf.SetVersion(nVersionIn);
+ vRecv.SetVersion(nVersionIn);
+ }
+
+ int readHeader(const char *pch, unsigned int nBytes);
+ int readData(const char *pch, unsigned int nBytes);
+};
+
+
+
+
/** Information about a peer */
class CNode
@@ -135,16 +173,21 @@ public:
// socket
uint64 nServices;
SOCKET hSocket;
- CDataStream vSend;
- CDataStream vRecv;
+ CDataStream ssSend;
+ size_t nSendSize; // total size of all vSendMsg entries
+ size_t nSendOffset; // offset inside the first vSendMsg already sent
+ std::deque<CSerializeData> vSendMsg;
CCriticalSection cs_vSend;
- CCriticalSection cs_vRecv;
+
+ std::deque<CInv> vRecvGetData;
+ std::deque<CNetMessage> vRecvMsg;
+ CCriticalSection cs_vRecvMsg;
+ int nRecvVersion;
+
int64 nLastSend;
int64 nLastRecv;
int64 nLastSendEmpty;
int64 nTimeConnected;
- int nHeaderStart;
- unsigned int nMessageStart;
CAddress addr;
std::string addrName;
CService addrLocal;
@@ -192,16 +235,15 @@ public:
CCriticalSection cs_inventory;
std::multimap<int64, CInv> mapAskFor;
- CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : vSend(SER_NETWORK, MIN_PROTO_VERSION), vRecv(SER_NETWORK, MIN_PROTO_VERSION)
+ CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, MIN_PROTO_VERSION)
{
nServices = 0;
hSocket = hSocketIn;
+ nRecvVersion = MIN_PROTO_VERSION;
nLastSend = 0;
nLastRecv = 0;
nLastSendEmpty = GetTime();
nTimeConnected = GetTime();
- nHeaderStart = -1;
- nMessageStart = -1;
addr = addrIn;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
nVersion = 0;
@@ -214,6 +256,8 @@ public:
fDisconnect = false;
nRefCount = 0;
nReleaseTime = 0;
+ nSendSize = 0;
+ nSendOffset = 0;
hashContinue = 0;
pindexLastGetBlocksBegin = 0;
hashLastGetBlocksEnd = 0;
@@ -251,6 +295,26 @@ public:
return std::max(nRefCount, 0) + (GetTime() < nReleaseTime ? 1 : 0);
}
+ // requires LOCK(cs_vRecvMsg)
+ unsigned int GetTotalRecvSize()
+ {
+ unsigned int total = 0;
+ BOOST_FOREACH(const CNetMessage &msg, vRecvMsg)
+ total += msg.vRecv.size() + 24;
+ return total;
+ }
+
+ // requires LOCK(cs_vRecvMsg)
+ bool ReceiveMsgBytes(const char *pch, unsigned int nBytes);
+
+ // requires LOCK(cs_vRecvMsg)
+ void SetRecvVersion(int nVersionIn)
+ {
+ nRecvVersion = nVersionIn;
+ BOOST_FOREACH(CNetMessage &msg, vRecvMsg)
+ msg.SetVersion(nVersionIn);
+ }
+
CNode* AddRef(int64 nTimeout=0)
{
if (nTimeout != 0)
@@ -325,11 +389,8 @@ public:
void BeginMessage(const char* pszCommand) EXCLUSIVE_LOCK_FUNCTION(cs_vSend)
{
ENTER_CRITICAL_SECTION(cs_vSend);
- if (nHeaderStart != -1)
- AbortMessage();
- nHeaderStart = vSend.size();
- vSend << CMessageHeader(pszCommand, 0);
- nMessageStart = vSend.size();
+ assert(ssSend.size() == 0);
+ ssSend << CMessageHeader(pszCommand, 0);
if (fDebug)
printf("sending: %s ", pszCommand);
}
@@ -337,11 +398,8 @@ public:
// TODO: Document the precondition of this function. Is cs_vSend locked?
void AbortMessage() UNLOCK_FUNCTION(cs_vSend)
{
- if (nHeaderStart < 0)
- return;
- vSend.resize(nHeaderStart);
- nHeaderStart = -1;
- nMessageStart = -1;
+ ssSend.clear();
+
LEAVE_CRITICAL_SECTION(cs_vSend);
if (fDebug)
@@ -358,26 +416,32 @@ public:
return;
}
- if (nHeaderStart < 0)
+ if (ssSend.size() == 0)
return;
// Set the size
- unsigned int nSize = vSend.size() - nMessageStart;
- memcpy((char*)&vSend[nHeaderStart] + CMessageHeader::MESSAGE_SIZE_OFFSET, &nSize, sizeof(nSize));
+ unsigned int nSize = ssSend.size() - CMessageHeader::HEADER_SIZE;
+ memcpy((char*)&ssSend[CMessageHeader::MESSAGE_SIZE_OFFSET], &nSize, sizeof(nSize));
// Set the checksum
- uint256 hash = Hash(vSend.begin() + nMessageStart, vSend.end());
+ uint256 hash = Hash(ssSend.begin() + CMessageHeader::HEADER_SIZE, ssSend.end());
unsigned int nChecksum = 0;
memcpy(&nChecksum, &hash, sizeof(nChecksum));
- assert(nMessageStart - nHeaderStart >= CMessageHeader::CHECKSUM_OFFSET + sizeof(nChecksum));
- memcpy((char*)&vSend[nHeaderStart] + CMessageHeader::CHECKSUM_OFFSET, &nChecksum, sizeof(nChecksum));
+ assert(ssSend.size () >= CMessageHeader::CHECKSUM_OFFSET + sizeof(nChecksum));
+ memcpy((char*)&ssSend[CMessageHeader::CHECKSUM_OFFSET], &nChecksum, sizeof(nChecksum));
if (fDebug) {
printf("(%d bytes)\n", nSize);
}
- nHeaderStart = -1;
- nMessageStart = -1;
+ std::deque<CSerializeData>::iterator it = vSendMsg.insert(vSendMsg.end(), CSerializeData());
+ ssSend.GetAndClear(*it);
+ nSendSize += (*it).size();
+
+ // If write queue empty, attempt "optimistic write"
+ if (it == vSendMsg.begin())
+ SocketSendData(this);
+
LEAVE_CRITICAL_SECTION(cs_vSend);
}
@@ -404,7 +468,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1;
+ ssSend << a1;
EndMessage();
}
catch (...)
@@ -420,7 +484,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2;
+ ssSend << a1 << a2;
EndMessage();
}
catch (...)
@@ -436,7 +500,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3;
+ ssSend << a1 << a2 << a3;
EndMessage();
}
catch (...)
@@ -452,7 +516,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4;
+ ssSend << a1 << a2 << a3 << a4;
EndMessage();
}
catch (...)
@@ -468,7 +532,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4 << a5;
+ ssSend << a1 << a2 << a3 << a4 << a5;
EndMessage();
}
catch (...)
@@ -484,7 +548,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4 << a5 << a6;
+ ssSend << a1 << a2 << a3 << a4 << a5 << a6;
EndMessage();
}
catch (...)
@@ -500,7 +564,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4 << a5 << a6 << a7;
+ ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7;
EndMessage();
}
catch (...)
@@ -516,7 +580,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8;
+ ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8;
EndMessage();
}
catch (...)
@@ -532,7 +596,7 @@ public:
try
{
BeginMessage(pszCommand);
- vSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9;
+ ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9;
EndMessage();
}
catch (...)
diff --git a/src/protocol.h b/src/protocol.h
index f5c162054e..4998425070 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -56,7 +56,8 @@ class CMessageHeader
CHECKSUM_SIZE=sizeof(int),
MESSAGE_SIZE_OFFSET=MESSAGE_START_SIZE+COMMAND_SIZE,
- CHECKSUM_OFFSET=MESSAGE_SIZE_OFFSET+MESSAGE_SIZE_SIZE
+ CHECKSUM_OFFSET=MESSAGE_SIZE_OFFSET+MESSAGE_SIZE_SIZE,
+ HEADER_SIZE=MESSAGE_START_SIZE+COMMAND_SIZE+MESSAGE_SIZE_SIZE+CHECKSUM_SIZE
};
char pchMessageStart[MESSAGE_START_SIZE];
char pchCommand[COMMAND_SIZE];
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp
index cf35ee2457..f165c11cb1 100644
--- a/src/qt/askpassphrasedialog.cpp
+++ b/src/qt/askpassphrasedialog.cpp
@@ -235,7 +235,7 @@ bool AskPassphraseDialog::eventFilter(QObject *object, QEvent *event)
if (str.length() != 0) {
const QChar *psz = str.unicode();
bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0;
- if ((fShift && psz->isLower()) || (!fShift && psz->isUpper())) {
+ if ((fShift && *psz >= 'a' && *psz <= 'z') || (!fShift && *psz >= 'A' && *psz <= 'Z')) {
fCapsLock = true;
ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!"));
} else if (psz->isLetter()) {
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index e3d51be54a..346128c0c4 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -232,7 +232,8 @@ int main(int argc, char *argv[])
WalletModel walletModel(pwalletMain, &optionsModel);
window.setClientModel(&clientModel);
- window.setWalletModel(&walletModel);
+ window.addWallet("~Default", &walletModel);
+ window.setCurrentWallet("~Default");
// If -min option passed, start window minimized.
if(GetBoolArg("-min"))
@@ -253,7 +254,7 @@ int main(int argc, char *argv[])
window.hide();
window.setClientModel(0);
- window.setWalletModel(0);
+ window.removeAllWallets();
guiref = 0;
}
// Shutdown the core and its threads, but don't exit Bitcoin-Qt here
diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp
index ddf185c415..4fa2ca508b 100644
--- a/src/qt/bitcoinamountfield.cpp
+++ b/src/qt/bitcoinamountfield.cpp
@@ -145,6 +145,11 @@ void BitcoinAmountField::unitChanged(int idx)
amount->setDecimals(BitcoinUnits::decimals(currentUnit));
amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals()));
+ if(currentUnit == BitcoinUnits::uBTC)
+ amount->setSingleStep(0.01);
+ else
+ amount->setSingleStep(0.001);
+
if(valid)
{
// If value was valid, re-place it in the widget with the new unit
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 0d951718bb..eff8e667f6 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -10,26 +10,20 @@
#include "bitcoingui.h"
#include "transactiontablemodel.h"
-#include "addressbookpage.h"
-#include "sendcoinsdialog.h"
-#include "signverifymessagedialog.h"
#include "optionsdialog.h"
#include "aboutdialog.h"
#include "clientmodel.h"
#include "walletmodel.h"
-#include "editaddressdialog.h"
+#include "walletframe.h"
#include "optionsmodel.h"
#include "transactiondescdialog.h"
-#include "addresstablemodel.h"
-#include "transactionview.h"
-#include "overviewpage.h"
#include "bitcoinunits.h"
#include "guiconstants.h"
-#include "askpassphrasedialog.h"
#include "notificator.h"
#include "guiutil.h"
#include "rpcconsole.h"
#include "ui_interface.h"
+#include "wallet.h"
#ifdef Q_OS_MAC
#include "macdockiconhandler.h"
@@ -54,13 +48,17 @@
#include <QUrl>
#include <QMimeData>
#include <QStyle>
+#include <QSettings>
+#include <QDesktopWidget>
+#include <QListWidget>
#include <iostream>
+const QString BitcoinGUI::DEFAULT_WALLET = "~Default";
+
BitcoinGUI::BitcoinGUI(QWidget *parent):
QMainWindow(parent),
clientModel(0),
- walletModel(0),
encryptWalletAction(0),
changePassphraseAction(0),
aboutQtAction(0),
@@ -69,7 +67,7 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
rpcConsole(0),
prevBlocks(0)
{
- resize(850, 550);
+ restoreWindowGeometry();
setWindowTitle(tr("Bitcoin") + " - " + tr("Wallet"));
#ifndef Q_OS_MAC
qApp->setWindowIcon(QIcon(":icons/bitcoin"));
@@ -93,31 +91,10 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
// Create system tray icon and notification
createTrayIcon();
- // Create tabs
- overviewPage = new OverviewPage();
-
- transactionsPage = new QWidget(this);
- QVBoxLayout *vbox = new QVBoxLayout();
- transactionView = new TransactionView(this);
- vbox->addWidget(transactionView);
- transactionsPage->setLayout(vbox);
-
- addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
-
- receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
-
- sendCoinsPage = new SendCoinsDialog(this);
-
- signVerifyMessageDialog = new SignVerifyMessageDialog(this);
-
- centralWidget = new QStackedWidget(this);
- centralWidget->addWidget(overviewPage);
- centralWidget->addWidget(transactionsPage);
- centralWidget->addWidget(addressBookPage);
- centralWidget->addWidget(receiveCoinsPage);
- centralWidget->addWidget(sendCoinsPage);
- setCentralWidget(centralWidget);
-
+ // Create wallet frame and make it the central widget
+ walletFrame = new WalletFrame(this);
+ setCentralWidget(walletFrame);
+
// Create status bar
statusBar();
@@ -162,31 +139,16 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
syncIconMovie = new QMovie(":/movies/update_spinner", "mng", this);
- // Clicking on a transaction on the overview page simply sends you to transaction history page
- connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
- connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
-
- // Double-clicking on a transaction on the transaction history page shows details
- connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
-
rpcConsole = new RPCConsole(this);
connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show()));
- // Clicking on "Send Coins" in the address book sends you to the send coins tab
- connect(addressBookPage, SIGNAL(sendCoins(QString)), this, SLOT(gotoSendCoinsPage(QString)));
- // Clicking on "Verify Message" in the address book opens the verify message tab in the Sign/Verify Message dialog
- connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString)));
- // Clicking on "Sign Message" in the receive coins page opens the sign message tab in the Sign/Verify Message dialog
- connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString)));
-
// Install event filter to be able to catch status tip events (QEvent::StatusTip)
this->installEventFilter(this);
-
- gotoOverviewPage();
}
BitcoinGUI::~BitcoinGUI()
{
+ saveWindowGeometry();
if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
trayIcon->hide();
#ifdef Q_OS_MAC
@@ -243,7 +205,7 @@ void BitcoinGUI::createActions()
connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
connect(addressBookAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized()));
connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
-
+
quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this);
quitAction->setStatusTip(tr("Quit application"));
quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
@@ -377,39 +339,23 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel)
// Receive and report messages from network/worker thread
connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int)));
- overviewPage->setClientModel(clientModel);
- rpcConsole->setClientModel(clientModel);
- addressBookPage->setOptionsModel(clientModel->getOptionsModel());
- receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
+ walletFrame->setClientModel(clientModel);
}
}
-void BitcoinGUI::setWalletModel(WalletModel *walletModel)
+bool BitcoinGUI::addWallet(const QString& name, WalletModel *walletModel)
{
- this->walletModel = walletModel;
- if(walletModel)
- {
- // Receive and report messages from wallet thread
- connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int)));
-
- // Put transaction list in tabs
- transactionView->setModel(walletModel);
- overviewPage->setWalletModel(walletModel);
- addressBookPage->setModel(walletModel->getAddressTableModel());
- receiveCoinsPage->setModel(walletModel->getAddressTableModel());
- sendCoinsPage->setModel(walletModel);
- signVerifyMessageDialog->setModel(walletModel);
-
- setEncryptionStatus(walletModel->getEncryptionStatus());
- connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
-
- // Balloon pop-up for new transaction
- connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(incomingTransaction(QModelIndex,int,int)));
-
- // Ask for passphrase if needed
- connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
- }
+ return walletFrame->addWallet(name, walletModel);
+}
+
+bool BitcoinGUI::setCurrentWallet(const QString& name)
+{
+ return walletFrame->setCurrentWallet(name);
+}
+
+void BitcoinGUI::removeAllWallets()
+{
+ walletFrame->removeAllWallets();
}
void BitcoinGUI::createTrayIcon()
@@ -468,6 +414,28 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
}
#endif
+void BitcoinGUI::saveWindowGeometry()
+{
+ QSettings settings;
+ settings.setValue("nWindowPos", pos());
+ settings.setValue("nWindowSize", size());
+}
+
+void BitcoinGUI::restoreWindowGeometry()
+{
+ QSettings settings;
+ QPoint pos = settings.value("nWindowPos").toPoint();
+ QSize size = settings.value("nWindowSize", QSize(850, 550)).toSize();
+ if (!pos.x() && !pos.y())
+ {
+ QRect screen = qApp->desktop()->screenGeometry();
+ pos.setX((screen.width()-size.width())/2);
+ pos.setY((screen.height()-size.height())/2);
+ }
+ resize(size);
+ move(pos);
+}
+
void BitcoinGUI::optionsClicked()
{
if(!clientModel || !clientModel->getOptionsModel())
@@ -484,6 +452,41 @@ void BitcoinGUI::aboutClicked()
dlg.exec();
}
+void BitcoinGUI::gotoOverviewPage()
+{
+ if (walletFrame) walletFrame->gotoOverviewPage();
+}
+
+void BitcoinGUI::gotoHistoryPage()
+{
+ if (walletFrame) walletFrame->gotoHistoryPage();
+}
+
+void BitcoinGUI::gotoAddressBookPage()
+{
+ if (walletFrame) walletFrame->gotoAddressBookPage();
+}
+
+void BitcoinGUI::gotoReceiveCoinsPage()
+{
+ if (walletFrame) walletFrame->gotoReceiveCoinsPage();
+}
+
+void BitcoinGUI::gotoSendCoinsPage()
+{
+ if (walletFrame) walletFrame->gotoSendCoinsPage();
+}
+
+void BitcoinGUI::gotoSignMessageTab(QString addr)
+{
+ if (walletFrame) walletFrame->gotoSignMessageTab(addr);
+}
+
+void BitcoinGUI::gotoVerifyMessageTab(QString addr)
+{
+ if (walletFrame) walletFrame->gotoSignMessageTab(addr);
+}
+
void BitcoinGUI::setNumConnections(int count)
{
QString icon;
@@ -548,7 +551,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
tooltip = tr("Up to date") + QString(".<br>") + tooltip;
labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
- overviewPage->showOutOfSyncWarning(false);
+ walletFrame->showOutOfSyncWarning(false);
progressBarLabel->setVisible(false);
progressBar->setVisible(false);
@@ -583,7 +586,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
syncIconMovie->jumpToNextFrame();
prevBlocks = count;
- overviewPage->showOutOfSyncWarning(true);
+ walletFrame->showOutOfSyncWarning(true);
tooltip += QString("<br>");
tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText);
@@ -692,104 +695,20 @@ void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee)
*payFee = (retval == QMessageBox::Yes);
}
-void BitcoinGUI::incomingTransaction(const QModelIndex& parent, int start, int /*end*/)
+void BitcoinGUI::incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address)
{
- // Prevent balloon-spam when initial block download is in progress
- if(!walletModel || !clientModel || clientModel->inInitialBlockDownload())
- return;
-
- TransactionTableModel *ttm = walletModel->getTransactionTableModel();
-
- QString date = ttm->index(start, TransactionTableModel::Date, parent)
- .data().toString();
- qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
- .data(Qt::EditRole).toULongLong();
- QString type = ttm->index(start, TransactionTableModel::Type, parent)
- .data().toString();
- QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
- .data().toString();
-
- // On new transaction, make an info balloon
+ // On new transaction, make an info balloon
message((amount)<0 ? tr("Sent transaction") : tr("Incoming transaction"),
tr("Date: %1\n"
"Amount: %2\n"
"Type: %3\n"
"Address: %4\n")
.arg(date)
- .arg(BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), amount, true))
+ .arg(BitcoinUnits::formatWithUnit(unit, amount, true))
.arg(type)
.arg(address), CClientUIInterface::MSG_INFORMATION);
}
-void BitcoinGUI::gotoOverviewPage()
-{
- overviewAction->setChecked(true);
- centralWidget->setCurrentWidget(overviewPage);
-
- exportAction->setEnabled(false);
- disconnect(exportAction, SIGNAL(triggered()), 0, 0);
-}
-
-void BitcoinGUI::gotoHistoryPage()
-{
- historyAction->setChecked(true);
- centralWidget->setCurrentWidget(transactionsPage);
-
- exportAction->setEnabled(true);
- disconnect(exportAction, SIGNAL(triggered()), 0, 0);
- connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
-}
-
-void BitcoinGUI::gotoAddressBookPage()
-{
- addressBookAction->setChecked(true);
- centralWidget->setCurrentWidget(addressBookPage);
-
- exportAction->setEnabled(true);
- disconnect(exportAction, SIGNAL(triggered()), 0, 0);
- connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
-}
-
-void BitcoinGUI::gotoReceiveCoinsPage()
-{
- receiveCoinsAction->setChecked(true);
- centralWidget->setCurrentWidget(receiveCoinsPage);
-
- exportAction->setEnabled(true);
- disconnect(exportAction, SIGNAL(triggered()), 0, 0);
- connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
-}
-
-void BitcoinGUI::gotoSendCoinsPage(QString addr)
-{
- sendCoinsAction->setChecked(true);
- centralWidget->setCurrentWidget(sendCoinsPage);
-
- exportAction->setEnabled(false);
- disconnect(exportAction, SIGNAL(triggered()), 0, 0);
-
- if(!addr.isEmpty())
- sendCoinsPage->setAddress(addr);
-}
-
-void BitcoinGUI::gotoSignMessageTab(QString addr)
-{
- // call show() in showTab_SM()
- signVerifyMessageDialog->showTab_SM(true);
-
- if(!addr.isEmpty())
- signVerifyMessageDialog->setAddress_SM(addr);
-}
-
-void BitcoinGUI::gotoVerifyMessageTab(QString addr)
-{
- // call show() in showTab_VM()
- signVerifyMessageDialog->showTab_VM(true);
-
- if(!addr.isEmpty())
- signVerifyMessageDialog->setAddress_VM(addr);
-}
-
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
{
// Accept only URIs
@@ -805,13 +724,13 @@ void BitcoinGUI::dropEvent(QDropEvent *event)
QList<QUrl> uris = event->mimeData()->urls();
foreach(const QUrl &uri, uris)
{
- if (sendCoinsPage->handleURI(uri.toString()))
+ if (walletFrame->handleURI(uri.toString()))
nValidUrisFound++;
}
// if valid URIs were found
if (nValidUrisFound)
- gotoSendCoinsPage();
+ walletFrame->gotoSendCoinsPage();
else
message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
CClientUIInterface::ICON_WARNING);
@@ -835,12 +754,7 @@ bool BitcoinGUI::eventFilter(QObject *object, QEvent *event)
void BitcoinGUI::handleURI(QString strURI)
{
// URI has to be valid
- if (sendCoinsPage->handleURI(strURI))
- {
- showNormalIfMinimized();
- gotoSendCoinsPage();
- }
- else
+ if (!walletFrame->handleURI(strURI))
message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
CClientUIInterface::ICON_WARNING);
}
@@ -876,49 +790,22 @@ void BitcoinGUI::setEncryptionStatus(int status)
void BitcoinGUI::encryptWallet(bool status)
{
- if(!walletModel)
- return;
- AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
- AskPassphraseDialog::Decrypt, this);
- dlg.setModel(walletModel);
- dlg.exec();
-
- setEncryptionStatus(walletModel->getEncryptionStatus());
+ walletFrame->encryptWallet(status);
}
void BitcoinGUI::backupWallet()
{
- QString saveDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
- QString filename = QFileDialog::getSaveFileName(this, tr("Backup Wallet"), saveDir, tr("Wallet Data (*.dat)"));
- if(!filename.isEmpty()) {
- if(!walletModel->backupWallet(filename)) {
- message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."),
- CClientUIInterface::MSG_ERROR);
- }
- else
- message(tr("Backup Successful"), tr("The wallet data was successfully saved to the new location."),
- CClientUIInterface::MSG_INFORMATION);
- }
+ walletFrame->backupWallet();
}
void BitcoinGUI::changePassphrase()
{
- AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
- dlg.setModel(walletModel);
- dlg.exec();
+ walletFrame->changePassphrase();
}
void BitcoinGUI::unlockWallet()
{
- if(!walletModel)
- return;
- // Unlock wallet when requested by wallet model
- if(walletModel->getEncryptionStatus() == WalletModel::Locked)
- {
- AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
- dlg.setModel(walletModel);
- dlg.exec();
- }
+ walletFrame->unlockWallet();
}
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 1b3e313fc2..750e97104c 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -3,10 +3,14 @@
#include <QMainWindow>
#include <QSystemTrayIcon>
+#include <QMap>
class TransactionTableModel;
+class WalletFrame;
+class WalletView;
class ClientModel;
class WalletModel;
+class WalletStack;
class TransactionView;
class OverviewPage;
class AddressBookPage;
@@ -15,11 +19,16 @@ class SignVerifyMessageDialog;
class Notificator;
class RPCConsole;
+class CWallet;
+
QT_BEGIN_NAMESPACE
class QLabel;
class QModelIndex;
class QProgressBar;
class QStackedWidget;
+class QUrl;
+class QListWidget;
+class QPushButton;
QT_END_NAMESPACE
/**
@@ -31,6 +40,8 @@ class BitcoinGUI : public QMainWindow
Q_OBJECT
public:
+ static const QString DEFAULT_WALLET;
+
explicit BitcoinGUI(QWidget *parent = 0);
~BitcoinGUI();
@@ -42,7 +53,11 @@ public:
The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending
functionality.
*/
- void setWalletModel(WalletModel *walletModel);
+
+ bool addWallet(const QString& name, WalletModel *walletModel);
+ bool setCurrentWallet(const QString& name);
+
+ void removeAllWallets();
protected:
void changeEvent(QEvent *e);
@@ -53,16 +68,7 @@ protected:
private:
ClientModel *clientModel;
- WalletModel *walletModel;
-
- QStackedWidget *centralWidget;
-
- OverviewPage *overviewPage;
- QWidget *transactionsPage;
- AddressBookPage *addressBookPage;
- AddressBookPage *receiveCoinsPage;
- SendCoinsDialog *sendCoinsPage;
- SignVerifyMessageDialog *signVerifyMessageDialog;
+ WalletFrame *walletFrame;
QLabel *labelEncryptionIcon;
QLabel *labelConnectionsIcon;
@@ -88,7 +94,7 @@ private:
QAction *changePassphraseAction;
QAction *aboutQtAction;
QAction *openRPCConsoleAction;
-
+
QSystemTrayIcon *trayIcon;
Notificator *notificator;
TransactionView *transactionView;
@@ -108,6 +114,10 @@ private:
void createTrayIcon();
/** Create system tray menu (or setup the dock menu) */
void createTrayIconMenu();
+ /** Save window size and position */
+ void saveWindowGeometry();
+ /** Restore window size and position */
+ void restoreWindowGeometry();
public slots:
/** Set number of connections shown in the UI */
@@ -139,6 +149,9 @@ public slots:
void askFee(qint64 nFeeRequired, bool *payFee);
void handleURI(QString strURI);
+ /** Show incoming transaction notification for new transactions. */
+ void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address);
+
private slots:
/** Switch to overview (home) page */
void gotoOverviewPage();
@@ -149,7 +162,7 @@ private slots:
/** Switch to receive coins page */
void gotoReceiveCoinsPage();
/** Switch to send coins page */
- void gotoSendCoinsPage(QString addr = "");
+ void gotoSendCoinsPage();
/** Show Sign/Verify Message dialog and switch to sign message tab */
void gotoSignMessageTab(QString addr = "");
@@ -164,11 +177,6 @@ private slots:
/** Handle tray icon clicked */
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
#endif
- /** Show incoming transaction notification for new transactions.
-
- The new items are those between start and end inclusive, under the given parent item.
- */
- void incomingTransaction(const QModelIndex& parent, int start, int /*end*/);
/** Encrypt the wallet */
void encryptWallet(bool status);
/** Backup the wallet */
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 30ad8ef66b..06ccde3782 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -161,7 +161,7 @@ void copyEntryData(QAbstractItemView *view, int column, int role)
if(!selection.isEmpty())
{
// Copy first item
- QApplication::clipboard()->setText(selection.at(0).data(role).toString());
+ QApplication::clipboard()->setText(selection.at(0).data(role).toString(),QClipboard::Selection);
}
}
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index e7c384161c..9240b71c71 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -123,6 +123,7 @@ TransactionView::TransactionView(QWidget *parent) :
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
+ QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
QAction *editLabelAction = new QAction(tr("Edit label"), this);
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
@@ -130,6 +131,7 @@ TransactionView::TransactionView(QWidget *parent) :
contextMenu->addAction(copyAddressAction);
contextMenu->addAction(copyLabelAction);
contextMenu->addAction(copyAmountAction);
+ contextMenu->addAction(copyTxIDAction);
contextMenu->addAction(editLabelAction);
contextMenu->addAction(showDetailsAction);
@@ -145,6 +147,7 @@ TransactionView::TransactionView(QWidget *parent) :
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
+ connect(copyTxIDAction, SIGNAL(triggered()), this, SLOT(copyTxID()));
connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
}
@@ -309,6 +312,11 @@ void TransactionView::copyAmount()
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
}
+void TransactionView::copyTxID()
+{
+ GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole);
+}
+
void TransactionView::editLabel()
{
if(!transactionView->selectionModel() ||!model)
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index e61515fdae..bb41a83e32 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -66,6 +66,7 @@ private slots:
void editLabel();
void copyLabel();
void copyAmount();
+ void copyTxID();
signals:
void doubleClicked(const QModelIndex&);
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp
new file mode 100644
index 0000000000..a53aa65466
--- /dev/null
+++ b/src/qt/walletframe.cpp
@@ -0,0 +1,129 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#include "walletframe.h"
+#include "bitcoingui.h"
+#include "walletstack.h"
+
+#include <QVBoxLayout>
+#include <QMessageBox>
+
+#include <stdio.h>
+
+WalletFrame::WalletFrame(BitcoinGUI *_gui) :
+ QFrame(_gui),
+ gui(_gui),
+ clientModel(0)
+{
+ // Leave HBox hook for adding a list view later
+ QHBoxLayout *walletFrameLayout = new QHBoxLayout(this);
+ walletStack = new WalletStack(this);
+ walletStack->setBitcoinGUI(gui);
+ walletFrameLayout->addWidget(walletStack);
+}
+
+WalletFrame::~WalletFrame()
+{
+}
+
+void WalletFrame::setClientModel(ClientModel *clientModel)
+{
+ this->clientModel = clientModel;
+ walletStack->setClientModel(clientModel);
+}
+
+bool WalletFrame::addWallet(const QString& name, WalletModel *walletModel)
+{
+ return walletStack->addWallet(name, walletModel);
+}
+
+bool WalletFrame::setCurrentWallet(const QString& name)
+{
+ // TODO: Check if valid name
+ walletStack->setCurrentWallet(name);
+ return true;
+}
+
+void WalletFrame::removeAllWallets()
+{
+ walletStack->removeAllWallets();
+}
+
+bool WalletFrame::handleURI(const QString &uri)
+{
+ return walletStack->handleURI(uri);
+}
+
+void WalletFrame::showOutOfSyncWarning(bool fShow)
+{
+ if (!walletStack) {
+ QMessageBox box;
+ box.setText("walletStack is null");
+ box.exec();
+ return;
+ }
+ walletStack->showOutOfSyncWarning(fShow);
+}
+
+void WalletFrame::gotoOverviewPage()
+{
+ walletStack->gotoOverviewPage();
+}
+
+void WalletFrame::gotoHistoryPage()
+{
+ walletStack->gotoHistoryPage();
+}
+
+void WalletFrame::gotoAddressBookPage()
+{
+ walletStack->gotoAddressBookPage();
+}
+
+void WalletFrame::gotoReceiveCoinsPage()
+{
+ walletStack->gotoReceiveCoinsPage();
+}
+
+void WalletFrame::gotoSendCoinsPage()
+{
+ walletStack->gotoSendCoinsPage();
+}
+
+void WalletFrame::gotoSignMessageTab(QString addr)
+{
+ walletStack->gotoSignMessageTab(addr);
+}
+
+void WalletFrame::gotoVerifyMessageTab(QString addr)
+{
+ walletStack->gotoSignMessageTab(addr);
+}
+
+void WalletFrame::encryptWallet(bool status)
+{
+ walletStack->encryptWallet(status);
+}
+
+void WalletFrame::backupWallet()
+{
+ walletStack->backupWallet();
+}
+
+void WalletFrame::changePassphrase()
+{
+ walletStack->changePassphrase();
+}
+
+void WalletFrame::unlockWallet()
+{
+ walletStack->unlockWallet();
+}
+
+void WalletFrame::setEncryptionStatus()
+{
+ walletStack->setEncryptionStatus();
+}
diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h
new file mode 100644
index 0000000000..5b4baf7255
--- /dev/null
+++ b/src/qt/walletframe.h
@@ -0,0 +1,73 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#ifndef WALLETFRAME_H
+#define WALLETFRAME_H
+
+#include <QFrame>
+
+class BitcoinGUI;
+class ClientModel;
+class WalletModel;
+class WalletStack;
+
+class WalletFrame : public QFrame
+{
+ Q_OBJECT
+public:
+ explicit WalletFrame(BitcoinGUI *_gui);
+ ~WalletFrame();
+
+ void setClientModel(ClientModel *clientModel);
+
+ bool addWallet(const QString& name, WalletModel *walletModel);
+ bool setCurrentWallet(const QString& name);
+
+ void removeAllWallets();
+
+ bool handleURI(const QString &uri);
+
+ void showOutOfSyncWarning(bool fShow);
+
+private:
+ BitcoinGUI *gui;
+ ClientModel *clientModel;
+ WalletStack *walletStack;
+
+public slots:
+ /** Switch to overview (home) page */
+ void gotoOverviewPage();
+ /** Switch to history (transactions) page */
+ void gotoHistoryPage();
+ /** Switch to address book page */
+ void gotoAddressBookPage();
+ /** Switch to receive coins page */
+ void gotoReceiveCoinsPage();
+ /** Switch to send coins page */
+ void gotoSendCoinsPage();
+
+ /** Show Sign/Verify Message dialog and switch to sign message tab */
+ void gotoSignMessageTab(QString addr = "");
+ /** Show Sign/Verify Message dialog and switch to verify message tab */
+ void gotoVerifyMessageTab(QString addr = "");
+
+ /** Encrypt the wallet */
+ void encryptWallet(bool status);
+ /** Backup the wallet */
+ void backupWallet();
+ /** Change encrypted wallet passphrase */
+ void changePassphrase();
+ /** Ask for passphrase to unlock wallet temporarily */
+ void unlockWallet();
+
+ /** Set the encryption status as shown in the UI.
+ @param[in] status current encryption status
+ @see WalletModel::EncryptionStatus
+ */
+ void setEncryptionStatus();
+};
+
+#endif // WALLETFRAME_H \ No newline at end of file
diff --git a/src/qt/walletstack.cpp b/src/qt/walletstack.cpp
new file mode 100644
index 0000000000..271d1c7924
--- /dev/null
+++ b/src/qt/walletstack.cpp
@@ -0,0 +1,155 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#include "walletstack.h"
+#include "walletview.h"
+#include "bitcoingui.h"
+
+#include <QMap>
+#include <QMessageBox>
+
+WalletStack::WalletStack(QWidget *parent) :
+ QStackedWidget(parent),
+ clientModel(0),
+ bOutOfSync(true)
+{
+}
+
+WalletStack::~WalletStack()
+{
+}
+
+bool WalletStack::addWallet(const QString& name, WalletModel *walletModel)
+{
+ if (!gui || !clientModel || mapWalletViews.count(name) > 0)
+ return false;
+
+ WalletView *walletView = new WalletView(this, gui);
+ walletView->setBitcoinGUI(gui);
+ walletView->setClientModel(clientModel);
+ walletView->setWalletModel(walletModel);
+ walletView->showOutOfSyncWarning(bOutOfSync);
+ addWidget(walletView);
+ mapWalletViews[name] = walletView;
+ return true;
+}
+
+bool WalletStack::removeWallet(const QString& name)
+{
+ if (mapWalletViews.count(name) == 0) return false;
+ WalletView *walletView = mapWalletViews.take(name);
+ removeWidget(walletView);
+ return true;
+}
+
+void WalletStack::removeAllWallets()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ removeWidget(i.value());
+ mapWalletViews.clear();
+}
+
+bool WalletStack::handleURI(const QString &uri)
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (!walletView) return false;
+
+ return walletView->handleURI(uri);
+}
+
+void WalletStack::showOutOfSyncWarning(bool fShow)
+{
+ bOutOfSync = fShow;
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->showOutOfSyncWarning(fShow);
+}
+
+void WalletStack::gotoOverviewPage()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->gotoOverviewPage();
+}
+
+void WalletStack::gotoHistoryPage()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->gotoHistoryPage();
+}
+
+void WalletStack::gotoAddressBookPage()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->gotoAddressBookPage();
+}
+
+void WalletStack::gotoReceiveCoinsPage()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->gotoReceiveCoinsPage();
+}
+
+void WalletStack::gotoSendCoinsPage()
+{
+ QMap<QString, WalletView*>::const_iterator i;
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i)
+ i.value()->gotoSendCoinsPage();
+}
+
+void WalletStack::gotoSignMessageTab(QString addr)
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->gotoSignMessageTab(addr);
+}
+
+void WalletStack::gotoVerifyMessageTab(QString addr)
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->gotoVerifyMessageTab(addr);
+}
+
+void WalletStack::encryptWallet(bool status)
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->encryptWallet(status);
+}
+
+void WalletStack::backupWallet()
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->backupWallet();
+}
+
+void WalletStack::changePassphrase()
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->changePassphrase();
+}
+
+void WalletStack::unlockWallet()
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->unlockWallet();
+}
+
+void WalletStack::setEncryptionStatus()
+{
+ WalletView *walletView = (WalletView*)currentWidget();
+ if (walletView) walletView->setEncryptionStatus();
+}
+
+void WalletStack::setCurrentWallet(const QString& name)
+{
+ if (mapWalletViews.count(name) == 0) return;
+ WalletView *walletView = mapWalletViews.value(name);
+ setCurrentWidget(walletView);
+ walletView->setEncryptionStatus();
+}
diff --git a/src/qt/walletstack.h b/src/qt/walletstack.h
new file mode 100644
index 0000000000..f3485816e9
--- /dev/null
+++ b/src/qt/walletstack.h
@@ -0,0 +1,102 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#ifndef WALLETSTACK_H
+#define WALLETSTACK_H
+
+#include <QStackedWidget>
+#include <QMap>
+#include <boost/shared_ptr.hpp>
+
+class BitcoinGUI;
+class TransactionTableModel;
+class ClientModel;
+class WalletModel;
+class WalletView;
+class TransactionView;
+class OverviewPage;
+class AddressBookPage;
+class SendCoinsDialog;
+class SignVerifyMessageDialog;
+class Notificator;
+class RPCConsole;
+
+class CWalletManager;
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QModelIndex;
+QT_END_NAMESPACE
+
+/*
+ WalletStack class. This class is a container for WalletView instances. It takes the place of centralWidget.
+ It was added to support multiple wallet functionality. It communicates with both the client and the
+ wallet models to give the user an up-to-date view of the current core state. It manages all the wallet views
+ it contains and updates them accordingly.
+ */
+class WalletStack : public QStackedWidget
+{
+ Q_OBJECT
+public:
+ explicit WalletStack(QWidget *parent = 0);
+ ~WalletStack();
+
+ void setBitcoinGUI(BitcoinGUI *gui) { this->gui = gui; }
+
+ void setClientModel(ClientModel *clientModel) { this->clientModel = clientModel; }
+
+ bool addWallet(const QString& name, WalletModel *walletModel);
+ bool removeWallet(const QString& name);
+
+ void removeAllWallets();
+
+ bool handleURI(const QString &uri);
+
+ void showOutOfSyncWarning(bool fShow);
+
+private:
+ BitcoinGUI *gui;
+ ClientModel *clientModel;
+ QMap<QString, WalletView*> mapWalletViews;
+
+ bool bOutOfSync;
+
+public slots:
+ void setCurrentWallet(const QString& name);
+
+ /** Switch to overview (home) page */
+ void gotoOverviewPage();
+ /** Switch to history (transactions) page */
+ void gotoHistoryPage();
+ /** Switch to address book page */
+ void gotoAddressBookPage();
+ /** Switch to receive coins page */
+ void gotoReceiveCoinsPage();
+ /** Switch to send coins page */
+ void gotoSendCoinsPage();
+
+ /** Show Sign/Verify Message dialog and switch to sign message tab */
+ void gotoSignMessageTab(QString addr = "");
+ /** Show Sign/Verify Message dialog and switch to verify message tab */
+ void gotoVerifyMessageTab(QString addr = "");
+
+ /** Encrypt the wallet */
+ void encryptWallet(bool status);
+ /** Backup the wallet */
+ void backupWallet();
+ /** Change encrypted wallet passphrase */
+ void changePassphrase();
+ /** Ask for passphrase to unlock wallet temporarily */
+ void unlockWallet();
+
+ /** Set the encryption status as shown in the UI.
+ @param[in] status current encryption status
+ @see WalletModel::EncryptionStatus
+ */
+ void setEncryptionStatus();
+};
+
+#endif // WALLETSTACK_H
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
new file mode 100644
index 0000000000..7dd36234c9
--- /dev/null
+++ b/src/qt/walletview.cpp
@@ -0,0 +1,354 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#include "walletview.h"
+#include "bitcoingui.h"
+#include "transactiontablemodel.h"
+#include "addressbookpage.h"
+#include "sendcoinsdialog.h"
+#include "signverifymessagedialog.h"
+#include "optionsdialog.h"
+#include "aboutdialog.h"
+#include "clientmodel.h"
+#include "walletmodel.h"
+#include "editaddressdialog.h"
+#include "optionsmodel.h"
+#include "transactiondescdialog.h"
+#include "addresstablemodel.h"
+#include "transactionview.h"
+#include "overviewpage.h"
+#include "bitcoinunits.h"
+#include "guiconstants.h"
+#include "askpassphrasedialog.h"
+#include "guiutil.h"
+#include "ui_interface.h"
+
+#include <QVBoxLayout>
+#include <QActionGroup>
+#include <QAction>
+#include <QLabel>
+#include <QDesktopServices>
+#include <QFileDialog>
+
+WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui):
+ QStackedWidget(parent),
+ gui(_gui),
+ clientModel(0),
+ walletModel(0),
+ encryptWalletAction(0),
+ changePassphraseAction(0)
+{
+ // Create actions for the toolbar, menu bar and tray/dock icon
+ createActions();
+
+ // Create tabs
+ overviewPage = new OverviewPage();
+
+ transactionsPage = new QWidget(this);
+ QVBoxLayout *vbox = new QVBoxLayout();
+ transactionView = new TransactionView(this);
+ vbox->addWidget(transactionView);
+ transactionsPage->setLayout(vbox);
+
+ addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
+
+ receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
+
+ sendCoinsPage = new SendCoinsDialog(gui);
+
+ signVerifyMessageDialog = new SignVerifyMessageDialog(gui);
+
+ addWidget(overviewPage);
+ addWidget(transactionsPage);
+ addWidget(addressBookPage);
+ addWidget(receiveCoinsPage);
+ addWidget(sendCoinsPage);
+
+ // Clicking on a transaction on the overview page simply sends you to transaction history page
+ connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage()));
+ connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
+
+ // Double-clicking on a transaction on the transaction history page shows details
+ connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails()));
+
+ // Clicking on "Verify Message" in the address book sends you to the verify message tab
+ connect(addressBookPage, SIGNAL(verifyMessage(QString)), this, SLOT(gotoVerifyMessageTab(QString)));
+ // Clicking on "Sign Message" in the receive coins page sends you to the sign message tab
+ connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString)));
+
+ gotoOverviewPage();
+}
+
+WalletView::~WalletView()
+{
+}
+
+void WalletView::createActions()
+{
+ QActionGroup *tabGroup = new QActionGroup(this);
+
+ overviewAction = new QAction(QIcon(":/icons/overview"), tr("&Overview"), this);
+ overviewAction->setStatusTip(tr("Show general overview of wallet"));
+ overviewAction->setToolTip(overviewAction->statusTip());
+ overviewAction->setCheckable(true);
+ overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
+ tabGroup->addAction(overviewAction);
+
+ sendCoinsAction = new QAction(QIcon(":/icons/send"), tr("&Send coins"), this);
+ sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
+ sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
+ sendCoinsAction->setCheckable(true);
+ sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
+ tabGroup->addAction(sendCoinsAction);
+
+ receiveCoinsAction = new QAction(QIcon(":/icons/receiving_addresses"), tr("&Receive coins"), this);
+ receiveCoinsAction->setStatusTip(tr("Show the list of addresses for receiving payments"));
+ receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
+ receiveCoinsAction->setCheckable(true);
+ receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
+ tabGroup->addAction(receiveCoinsAction);
+
+ historyAction = new QAction(QIcon(":/icons/history"), tr("&Transactions"), this);
+ historyAction->setStatusTip(tr("Browse transaction history"));
+ historyAction->setToolTip(historyAction->statusTip());
+ historyAction->setCheckable(true);
+ historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
+ tabGroup->addAction(historyAction);
+
+ addressBookAction = new QAction(QIcon(":/icons/address-book"), tr("&Address Book"), this);
+ addressBookAction->setStatusTip(tr("Edit the list of stored addresses and labels"));
+ addressBookAction->setToolTip(addressBookAction->statusTip());
+ addressBookAction->setCheckable(true);
+ addressBookAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5));
+ tabGroup->addAction(addressBookAction);
+
+ connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage()));
+ connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage()));
+ connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage()));
+ connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
+ connect(addressBookAction, SIGNAL(triggered()), this, SLOT(gotoAddressBookPage()));
+
+ encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this);
+ encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet"));
+ encryptWalletAction->setCheckable(true);
+ backupWalletAction = new QAction(QIcon(":/icons/filesave"), tr("&Backup Wallet..."), this);
+ backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
+ changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase..."), this);
+ changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
+ signMessageAction = new QAction(QIcon(":/icons/edit"), tr("Sign &message..."), this);
+ signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
+ verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this);
+ verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
+
+ exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
+ exportAction->setStatusTip(tr("Export the data in the current tab to a file"));
+ exportAction->setToolTip(exportAction->statusTip());
+
+ connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
+ connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
+ connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
+ connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab()));
+ connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab()));
+}
+
+void WalletView::setBitcoinGUI(BitcoinGUI *gui)
+{
+ this->gui = gui;
+}
+
+void WalletView::setClientModel(ClientModel *clientModel)
+{
+ this->clientModel = clientModel;
+ if(clientModel)
+ {
+ overviewPage->setClientModel(clientModel);
+ addressBookPage->setOptionsModel(clientModel->getOptionsModel());
+ receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
+ }
+}
+
+void WalletView::setWalletModel(WalletModel *walletModel)
+{
+ this->walletModel = walletModel;
+ if(walletModel)
+ {
+ // Receive and report messages from wallet thread
+ connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), gui, SLOT(message(QString,QString,unsigned int)));
+
+ // Put transaction list in tabs
+ transactionView->setModel(walletModel);
+ overviewPage->setWalletModel(walletModel);
+ addressBookPage->setModel(walletModel->getAddressTableModel());
+ receiveCoinsPage->setModel(walletModel->getAddressTableModel());
+ sendCoinsPage->setModel(walletModel);
+ signVerifyMessageDialog->setModel(walletModel);
+
+ setEncryptionStatus();
+ connect(walletModel, SIGNAL(encryptionStatusChanged(int)), gui, SLOT(setEncryptionStatus(int)));
+
+ // Balloon pop-up for new transaction
+ connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
+ this, SLOT(incomingTransaction(QModelIndex,int,int)));
+
+ // Ask for passphrase if needed
+ connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
+ }
+}
+
+void WalletView::incomingTransaction(const QModelIndex& parent, int start, int /*end*/)
+{
+ // Prevent balloon-spam when initial block download is in progress
+ if(!walletModel || !clientModel || clientModel->inInitialBlockDownload())
+ return;
+
+ TransactionTableModel *ttm = walletModel->getTransactionTableModel();
+
+ QString date = ttm->index(start, TransactionTableModel::Date, parent)
+ .data().toString();
+ qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
+ .data(Qt::EditRole).toULongLong();
+ QString type = ttm->index(start, TransactionTableModel::Type, parent)
+ .data().toString();
+ QString address = ttm->index(start, TransactionTableModel::ToAddress, parent)
+ .data().toString();
+
+ gui->incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address);
+}
+
+void WalletView::gotoOverviewPage()
+{
+ overviewAction->setChecked(true);
+ setCurrentWidget(overviewPage);
+
+ exportAction->setEnabled(false);
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0);
+}
+
+void WalletView::gotoHistoryPage()
+{
+ historyAction->setChecked(true);
+ setCurrentWidget(transactionsPage);
+
+ exportAction->setEnabled(true);
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0);
+ connect(exportAction, SIGNAL(triggered()), transactionView, SLOT(exportClicked()));
+}
+
+void WalletView::gotoAddressBookPage()
+{
+ addressBookAction->setChecked(true);
+ setCurrentWidget(addressBookPage);
+
+ exportAction->setEnabled(true);
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0);
+ connect(exportAction, SIGNAL(triggered()), addressBookPage, SLOT(exportClicked()));
+}
+
+void WalletView::gotoReceiveCoinsPage()
+{
+ receiveCoinsAction->setChecked(true);
+ setCurrentWidget(receiveCoinsPage);
+
+ exportAction->setEnabled(true);
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0);
+ connect(exportAction, SIGNAL(triggered()), receiveCoinsPage, SLOT(exportClicked()));
+}
+
+void WalletView::gotoSendCoinsPage()
+{
+ sendCoinsAction->setChecked(true);
+ setCurrentWidget(sendCoinsPage);
+
+ exportAction->setEnabled(false);
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0);
+}
+
+void WalletView::gotoSignMessageTab(QString addr)
+{
+ // call show() in showTab_SM()
+ signVerifyMessageDialog->showTab_SM(true);
+
+ if(!addr.isEmpty())
+ signVerifyMessageDialog->setAddress_SM(addr);
+}
+
+void WalletView::gotoVerifyMessageTab(QString addr)
+{
+ // call show() in showTab_VM()
+ signVerifyMessageDialog->showTab_VM(true);
+
+ if(!addr.isEmpty())
+ signVerifyMessageDialog->setAddress_VM(addr);
+}
+
+bool WalletView::handleURI(const QString& strURI)
+{
+ // URI has to be valid
+ if (sendCoinsPage->handleURI(strURI))
+ {
+ gotoSendCoinsPage();
+ return true;
+ }
+ else
+ return false;
+}
+
+void WalletView::showOutOfSyncWarning(bool fShow)
+{
+ overviewPage->showOutOfSyncWarning(fShow);
+}
+
+void WalletView::setEncryptionStatus()
+{
+ gui->setEncryptionStatus(walletModel->getEncryptionStatus());
+}
+
+void WalletView::encryptWallet(bool status)
+{
+ if(!walletModel)
+ return;
+ AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
+ AskPassphraseDialog::Decrypt, this);
+ dlg.setModel(walletModel);
+ dlg.exec();
+
+ setEncryptionStatus();
+}
+
+void WalletView::backupWallet()
+{
+ QString saveDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
+ QString filename = QFileDialog::getSaveFileName(this, tr("Backup Wallet"), saveDir, tr("Wallet Data (*.dat)"));
+ if(!filename.isEmpty()) {
+ if(!walletModel->backupWallet(filename)) {
+ gui->message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to the new location."),
+ CClientUIInterface::MSG_ERROR);
+ }
+ else
+ gui->message(tr("Backup Successful"), tr("The wallet data was successfully saved to the new location."),
+ CClientUIInterface::MSG_INFORMATION);
+ }
+}
+
+void WalletView::changePassphrase()
+{
+ AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
+ dlg.setModel(walletModel);
+ dlg.exec();
+}
+
+void WalletView::unlockWallet()
+{
+ if(!walletModel)
+ return;
+ // Unlock wallet when requested by wallet model
+ if(walletModel->getEncryptionStatus() == WalletModel::Locked)
+ {
+ AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
+ dlg.setModel(walletModel);
+ dlg.exec();
+ }
+}
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
new file mode 100644
index 0000000000..38eb0227af
--- /dev/null
+++ b/src/qt/walletview.h
@@ -0,0 +1,132 @@
+/*
+ * Qt4 bitcoin GUI.
+ *
+ * W.J. van der Laan 2011-2012
+ * The Bitcoin Developers 2011-2013
+ */
+#ifndef WALLETVIEW_H
+#define WALLETVIEW_H
+
+#include <QStackedWidget>
+
+class BitcoinGUI;
+class TransactionTableModel;
+class ClientModel;
+class WalletModel;
+class TransactionView;
+class OverviewPage;
+class AddressBookPage;
+class SendCoinsDialog;
+class SignVerifyMessageDialog;
+class Notificator;
+class RPCConsole;
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QModelIndex;
+QT_END_NAMESPACE
+
+/*
+ WalletView class. This class represents the view to a single wallet.
+ It was added to support multiple wallet functionality. Each wallet gets its own WalletView instance.
+ It communicates with both the client and the wallet models to give the user an up-to-date view of the
+ current core state.
+*/
+class WalletView : public QStackedWidget
+{
+ Q_OBJECT
+public:
+ explicit WalletView(QWidget *parent, BitcoinGUI *_gui);
+ ~WalletView();
+
+ void setBitcoinGUI(BitcoinGUI *gui);
+ /** Set the client model.
+ The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic.
+ */
+ void setClientModel(ClientModel *clientModel);
+ /** Set the wallet model.
+ The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending
+ functionality.
+ */
+ void setWalletModel(WalletModel *walletModel);
+
+ bool handleURI(const QString &uri);
+
+ void showOutOfSyncWarning(bool fShow);
+
+private:
+ BitcoinGUI *gui;
+ ClientModel *clientModel;
+ WalletModel *walletModel;
+
+ OverviewPage *overviewPage;
+ QWidget *transactionsPage;
+ AddressBookPage *addressBookPage;
+ AddressBookPage *receiveCoinsPage;
+ SendCoinsDialog *sendCoinsPage;
+ SignVerifyMessageDialog *signVerifyMessageDialog;
+
+ QLabel *labelEncryptionIcon;
+ QLabel *labelConnectionsIcon;
+ QLabel *labelBlocksIcon;
+ QLabel *progressBarLabel;
+
+ QAction *overviewAction;
+ QAction *historyAction;
+ QAction *quitAction;
+ QAction *sendCoinsAction;
+ QAction *addressBookAction;
+ QAction *signMessageAction;
+ QAction *verifyMessageAction;
+ QAction *aboutAction;
+ QAction *receiveCoinsAction;
+ QAction *optionsAction;
+ QAction *toggleHideAction;
+ QAction *exportAction;
+ QAction *encryptWalletAction;
+ QAction *backupWalletAction;
+ QAction *changePassphraseAction;
+ QAction *aboutQtAction;
+ QAction *openRPCConsoleAction;
+
+ TransactionView *transactionView;
+
+ /** Create the main UI actions. */
+ void createActions();
+ /** Create the menu bar and sub-menus. */
+
+public slots:
+ /** Switch to overview (home) page */
+ void gotoOverviewPage();
+ /** Switch to history (transactions) page */
+ void gotoHistoryPage();
+ /** Switch to address book page */
+ void gotoAddressBookPage();
+ /** Switch to receive coins page */
+ void gotoReceiveCoinsPage();
+ /** Switch to send coins page */
+ void gotoSendCoinsPage();
+
+ /** Show Sign/Verify Message dialog and switch to sign message tab */
+ void gotoSignMessageTab(QString addr = "");
+ /** Show Sign/Verify Message dialog and switch to verify message tab */
+ void gotoVerifyMessageTab(QString addr = "");
+
+ /** Show incoming transaction notification for new transactions.
+
+ The new items are those between start and end inclusive, under the given parent item.
+ */
+ void incomingTransaction(const QModelIndex& parent, int start, int /*end*/);
+ /** Encrypt the wallet */
+ void encryptWallet(bool status);
+ /** Backup the wallet */
+ void backupWallet();
+ /** Change encrypted wallet passphrase */
+ void changePassphrase();
+ /** Ask for passphrase to unlock wallet temporarily */
+ void unlockWallet();
+
+ void setEncryptionStatus();
+};
+
+#endif // WALLETVIEW_H
diff --git a/src/serialize.h b/src/serialize.h
index f2626281c1..e3d9939bcc 100644
--- a/src/serialize.h
+++ b/src/serialize.h
@@ -789,6 +789,7 @@ struct ser_streamplaceholder
+typedef std::vector<char, zero_after_free_allocator<char> > CSerializeData;
/** Double ended buffer combining vector and stream-like interfaces.
*
@@ -798,7 +799,7 @@ struct ser_streamplaceholder
class CDataStream
{
protected:
- typedef std::vector<char, zero_after_free_allocator<char> > vector_type;
+ typedef CSerializeData vector_type;
vector_type vch;
unsigned int nReadPos;
short state;
@@ -1095,6 +1096,11 @@ public:
::Unserialize(*this, obj, nType, nVersion);
return (*this);
}
+
+ void GetAndClear(CSerializeData &data) {
+ vch.swap(data);
+ CSerializeData().swap(vch);
+ }
};
diff --git a/src/test/alert_tests.cpp b/src/test/alert_tests.cpp
new file mode 100644
index 0000000000..f7a11376d3
--- /dev/null
+++ b/src/test/alert_tests.cpp
@@ -0,0 +1,185 @@
+//
+// Unit tests for alert system
+//
+
+#include <boost/foreach.hpp>
+#include <boost/test/unit_test.hpp>
+#include <fstream>
+
+#include "alert.h"
+#include "serialize.h"
+#include "util.h"
+
+#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 = 70001;
+ 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
+{
+ ReadAlerts()
+ {
+ std::string filename("alertTests");
+ namespace fs = boost::filesystem;
+ fs::path testFile = fs::current_path() / "test" / "data" / filename;
+#ifdef TEST_DATA_DIR
+ if (!fs::exists(testFile))
+ {
+ testFile = fs::path(BOOST_PP_STRINGIZE(TEST_DATA_DIR)) / filename;
+ }
+#endif
+ FILE* fp = fopen(testFile.string().c_str(), "rb");
+ if (!fp) return;
+
+
+ CAutoFile filein = CAutoFile(fp, SER_DISK, CLIENT_VERSION);
+ if (!filein) return;
+
+ try {
+ while (!feof(filein))
+ {
+ CAlert alert;
+ filein >> alert;
+ alerts.push_back(alert);
+ }
+ }
+ catch (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);
+
+ BOOST_FOREACH(const CAlert& alert, alerts)
+ {
+ BOOST_CHECK(alert.CheckSignature());
+ }
+ // Matches:
+ BOOST_CHECK(alerts[0].AppliesTo(1, ""));
+ BOOST_CHECK(alerts[0].AppliesTo(70001, ""));
+ 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(70001, "/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(70002, ""));
+
+ 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(70002, "/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);
+}
+
+
+// This uses sh 'echo' to test the -alertnotify function, writing to a
+// /tmp file. So skip it on Windows:
+#ifndef WIN32
+BOOST_AUTO_TEST_CASE(AlertNotify)
+{
+ SetMockTime(11);
+
+ boost::filesystem::path temp = GetTempPath() / "alertnotify.txt";
+ boost::filesystem::remove(temp);
+
+ mapArgs["-alertnotify"] = std::string("echo %s >> ") + temp.string();
+
+ BOOST_FOREACH(CAlert alert, alerts)
+ alert.ProcessAlert(false);
+
+ std::vector<std::string> r = read_lines(temp);
+ BOOST_CHECK_EQUAL(r.size(), 4u);
+ 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
+
+ boost::filesystem::remove(temp);
+
+ SetMockTime(0);
+}
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/data/alertTests b/src/test/data/alertTests
new file mode 100644
index 0000000000..7fc4528961
--- /dev/null
+++ b/src/test/data/alertTests
Binary files differ